#include <TFT_eSPI.h> // Hardware-specific library
#include <SPI.h>
#define RED2RED 0
#define GREEN2GREEN 1
#define BLUE2BLUE 2
#define BLUE2RED 3
#define GREEN2RED 4
#define RED2GREEN 5
#define TFT_GREY 0x2104
#define TdsSensorPin 36
#define VREF 3.3 // analog reference voltage(Volt) of the ADC
#define SCOUNT 30 // sum of sample point
int analogBuffer[SCOUNT]; // store the analog value in the array, read from ADC
int analogBufferTemp[SCOUNT];
int analogBufferIndex = 0,copyIndex = 0;
float averageVoltage = 0,tdsValue = 0,temperature = 25;
TFT_eSPI tft = TFT_eSPI(); // Invoke custom library with default width and height
uint32_t runTime = -99999; // time for next update
int d = 0; // Variable used for the sinewave test waveform
boolean range_error = 0;
void setup()
{
Serial.begin(115200);
pinMode(TdsSensorPin,INPUT);
tft.begin();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString("Makerfabs", 10, 10, 4);
tft.drawString("TDS value", 350, 10, 2);
}
void loop()
{
static unsigned long analogSampleTimepoint = millis();
if(millis()-analogSampleTimepoint > 40U) //every 40 milliseconds,read the analog value from the ADC
{
analogSampleTimepoint = millis();
analogBuffer[analogBufferIndex] = analogRead(TdsSensorPin); //read the analog value and store into the buffer
analogBufferIndex++;
if(analogBufferIndex == SCOUNT)
analogBufferIndex = 0;
}
static unsigned long printTimepoint = millis();
if(millis()-printTimepoint > 800U)
{
printTimepoint = millis();
for(copyIndex=0;copyIndex<SCOUNT;copyIndex++)
analogBufferTemp[copyIndex]= analogBuffer[copyIndex];
averageVoltage = getMedianNum(analogBufferTemp,SCOUNT) * (float)VREF / 1024.0; // read the analog value more stable by the median filtering algorithm, and convert to voltage value
float compensationCoefficient=1.0+0.02*(temperature-25.0); //temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0));
float compensationVolatge=averageVoltage/compensationCoefficient; //temperature compensation
tdsValue=(133.42*compensationVolatge*compensationVolatge*compensationVolatge - 255.86*compensationVolatge*compensationVolatge + 857.39*compensationVolatge)*0.5; //convert voltage value to tds value
//Serial.print("voltage:");
//Serial.print(averageVoltage,2);
//Serial.print("V ");
Serial.print("TDS Value:");
Serial.print(tdsValue,0);
Serial.println("ppm");
Serial.print("ADC:");
DisplayValue((int)tdsValue);
}
}
int getMedianNum(int bArray[], int iFilterLen)
{
int bTab[iFilterLen];
for (byte i = 0; i<iFilterLen; i++)
bTab[i] = bArray[i];
int i, j, bTemp;
for (j = 0; j < iFilterLen - 1; j++)
{
for (i = 0; i < iFilterLen - j - 1; i++)
{
if (bTab[i] > bTab[i + 1])
{
bTemp = bTab[i];
bTab[i] = bTab[i + 1];
bTab[i + 1] = bTemp;
}
}
}
if ((iFilterLen & 1) > 0)
bTemp = bTab[(iFilterLen - 1) / 2];
else
bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
return bTemp;
}
void DisplayValue(int value)
{
if (millis() - runTime >= 0L)
{ // Execute every TBD ms
runTime = millis();
// Test with a slowly changing value from a Sine function
//d += 4; if (d >= 360) d = 0;
// Set the the position, gap between meters, and inner radius of the meters
int xpos = 0, ypos = 5, gap = 4, radius = 52;
// Draw meter and get back x position of next meter
// Test with Sine wave function, normally reading will be from a sensor
//reading = 250 + 250 * sineWave(d+0);
//xpos = gap + ringMeter(reading, 0, 500, xpos, ypos, radius, "mA", GREEN2RED); // Draw analogue meter
//reading = 20 + 30 * sineWave(d+60);
//xpos = gap + ringMeter(reading, -10, 50, xpos, ypos, radius, "degC", BLUE2RED); // Draw analogue meter
//reading = 50 + 50 * sineWave(d + 120);
//ringMeter(reading, 0, 100, xpos, ypos, radius, "%RH", BLUE2BLUE); // Draw analogue meter
// Draw two more larger meters
//xpos = 20, ypos = 115, gap = 24, radius = 64;
//reading = 1000 + 150 * sineWave(d + 90);
//xpos = gap + ringMeter(reading, 850, 1150, xpos, ypos, radius, "mb", BLUE2RED); // Draw analogue meter
//reading = 15 + 15 * sineWave(d + 150);
//xpos = gap + ringMeter(reading, 0, 30, xpos, ypos, radius, "Volts", GREEN2GREEN); // Draw analogue meter
// Draw a large meter
xpos = 480 / 2 - 160, ypos = 0, gap = 15, radius = 170;
// Comment out above meters, then uncomment the next line to show large meter
ringMeter(value, 0, 1000, xpos, ypos, radius, "ppm", GREEN2RED); // Draw analogue meter
}
}
// #########################################################################
// Draw the meter on the screen, returns x coord of righthand side
// #########################################################################
int ringMeter(int value, int vmin, int vmax, int x, int y, int r, const char *units, byte scheme)
{
// Minimum value of r is about 52 before value text intrudes on ring
// drawing the text first is an option
x += r;
y += r; // Calculate coords of centre of ring
int w = r / 3; // Width of outer ring is 1/4 of radius
int angle = 150; // Half the sweep angle of meter (300 degrees)
int v = map(value, vmin, vmax, -angle, angle); // Map the value to an angle v
byte seg = 3; // Segments are 3 degrees wide = 100 segments for 300 degrees
byte inc = 6; // Draw segments every 3 degrees, increase to 6 for segmented ring
// Variable to save "value" text colour from scheme and set default
int colour = TFT_BLUE;
// Draw colour blocks every inc degrees
for (int i = -angle + inc / 2; i < angle - inc / 2; i += inc)
{
// Calculate pair of coordinates for segment start
float sx = cos((i - 90) * 0.0174532925);
float sy = sin((i - 90) * 0.0174532925);
uint16_t x0 = sx * (r - w) + x;
uint16_t y0 = sy * (r - w) + y;
uint16_t x1 = sx * r + x;
uint16_t y1 = sy * r + y;
// Calculate pair of coordinates for segment end
float sx2 = cos((i + seg - 90) * 0.0174532925);
float sy2 = sin((i + seg - 90) * 0.0174532925);
int x2 = sx2 * (r - w) + x;
int y2 = sy2 * (r - w) + y;
int x3 = sx2 * r + x;
int y3 = sy2 * r + y;
if (i < v)
{ // Fill in coloured segments with 2 triangles
switch (scheme)
{
case 0:
colour = TFT_RED;
break; // Fixed colour
case 1:
colour = TFT_GREEN;
break; // Fixed colour
case 2:
colour = TFT_BLUE;
break; // Fixed colour
case 3:
colour = rainbow(map(i, -angle, angle, 0, 127));
break; // Full spectrum blue to red
case 4:
colour = rainbow(map(i, -angle, angle, 70, 127));
break; // Green to red (high temperature etc)
case 5:
colour = rainbow(map(i, -angle, angle, 127, 63));
break; // Red to green (low battery etc)
default:
colour = TFT_BLUE;
break; // Fixed colour
}
tft.fillTriangle(x0, y0, x1, y1, x2, y2, colour);
tft.fillTriangle(x1, y1, x2, y2, x3, y3, colour);
//text_colour = colour; // Save the last colour drawn
}
else // Fill in blank segments
{
tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREY);
tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREY);
}
}
// Convert value to a string
char buf[10];
byte len = 3;
if (value > 999)
len = 5;
dtostrf(value, len, 0, buf);
buf[len] = ' ';
buf[len + 1] = 0; // Add blanking space and terminator, helps to centre text too!
// Set the text colour to default
tft.setTextSize(1);
/*if (value < vmin || value > vmax)
{
drawAlert(x, y + 90, 50, 1);
}
else
{
drawAlert(x, y + 90, 50, 0);
}*/
tft.setTextColor(TFT_WHITE, TFT_BLACK);
// Uncomment next line to set the text colour to the last segment value!
tft.setTextColor(colour, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
// Print value, if the meter is large then use big font 8, othewise use 4
if (r > 84)
{
tft.setTextPadding(55 * 3); // Allow for 3 digits each 55 pixels wide
tft.drawString(buf, x, y, 8); // Value in middle
}
else
{
tft.setTextPadding(3 * 14); // Allow for 3 digits each 14 pixels wide
tft.drawString(buf, x, y, 4); // Value in middle
}
tft.setTextSize(1);
tft.setTextPadding(0);
// Print units, if the meter is large then use big font 4, othewise use 2
tft.setTextColor(TFT_WHITE, TFT_BLACK);
if (r > 84)
tft.drawString(units, x, y + 60, 4); // Units display
else
tft.drawString(units, x, y + 15, 2); // Units display
// Calculate and return right hand side x coordinate
return x + r;
}
/*void drawAlert(int x, int y, int side, boolean draw)
{
if (draw && !range_error)
{
drawIcon(alert, x - alertWidth / 2, y - alertHeight / 2, alertWidth, alertHeight);
range_error = 1;
}
else if (!draw)
{
tft.fillRect(x - alertWidth / 2, y - alertHeight / 2, alertWidth, alertHeight, TFT_BLACK);
range_error = 0;
}
}
*/
// #########################################################################
// Return a 16 bit rainbow colour
// #########################################################################
unsigned int rainbow(byte value)
{
// Value is expected to be in range 0-127
// The value is converted to a spectrum colour from 0 = blue through to 127 = red
byte red = 0; // Red is the top 5 bits of a 16 bit colour value
byte green = 0; // Green is the middle 6 bits
byte blue = 0; // Blue is the bottom 5 bits
byte quadrant = value / 32;
if (quadrant == 0)
{
blue = 31;
green = 2 * (value % 32);
red = 0;
}
if (quadrant == 1)
{
blue = 31 - (value % 32);
green = 63;
red = 0;
}
if (quadrant == 2)
{
blue = 0;
green = 63;
red = value % 32;
}
if (quadrant == 3)
{
blue = 0;
green = 63 - 2 * (value % 32);
red = 31;
}
return (red << 11) + (green << 5) + blue;
}
// #########################################################################
// Return a value in range -1 to +1 for a given phase angle in degrees
// #########################################################################
float sineWave(int phase)
{
return sin(phase * 0.0174532925);
}