#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <math.h>
#define TFT_DC 2
#define TFT_CS 15
#define ILI9341_DARKCYAN #013238
#define ILI9341_LIGHTCYAN #013238
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
// x = 320, y = 240
#define CX 100 //Ceter of circle x co-ordinate top left to rightr
#define CY 120 //Ceter of circle y co-ordinate top left to down
#define RADIUS 95 //radius of circle
#define STARTANGLE 135 // start angle of the gauge
#define STOPANGLE 405 // stop angle of the gauge
#define NUMMARKS 13 // no. of marks on the gauge
#define BATX 200 // battery x position
#define BATY 30 // battery y position
#define BATW 50 // battery width
#define BATH 15 // battery height
#define MIN_VOLTAGE 3.0 // Battery's minimum voltage level
#define MAX_VOLTAGE 4.2 // Battery's maximum voltage leve;
#define VX 210 // Voltage icon x co-ordinate
#define VY 90 // Voltage icon y co-ordinate
#define RANX 210 // Range icon x co-ordinate
#define RANY 150 // Range icon y co-ordinate
uint8_t step = (float)((STOPANGLE - STARTANGLE) / NUMMARKS);
uint16_t nox, noy, nix, niy, ESPfillW, MOTORfillW; // needle co-ordinates
void updateBatteryESP(); // updates the ESP's battery level and %
void updateBatteryMOTOR(); // updates the motor battery level and %
void drawOutline(); // draw the outline of batteries, range, voltage and temp icon
void drawGauge(); // draw the gauge with marks and speed labels
void drawNeedle(); // draw the speedometer needle and speed
void updateValues(); // update distance ravelled and range
struct DATA
{
uint8_t speed, dist, range, temp;
uint16_t BAT_ADC_ESP=805, BAT_ADC_MOTOR=805;
bool ESPcharging=1, MOTORcharging=1;
}data;
void setup() {
Serial.begin(115200);
tft.begin();
tft.setRotation(1);
tft.setTextColor(ILI9341_WHITE);
tft.fillScreen(ILI9341_BLACK);
drawGauge();
drawOutline();
}
void loop()
{
updateValues();
updateBatteryMOTOR();
updateBatteryESP();
}
void updateValues()
{
// RANGE text
tft.fillRect(RANX+30, RANY+25, 30, 10, ILI9341_BLACK); // erase value for new update
tft.setCursor(RANX+30, RANY+20); // cursor position for range no.
tft.println("240");
tft.setCursor(RANX+50, RANY+20); // cursor position for Range KM
tft.println("KM");
// Temp text
tft.fillRect(RANX+85, RANY+20, 30, 10, ILI9341_BLACK); // erase value for new update
tft.setCursor(RANX+85, RANY+20);
tft.println(data.temp);
tft.setCursor(RANX+100, RANY+20);
tft.println("C");
// ODOMETER 100 120
tft.fillRect(CX-15, CY+80, 30, 10, ILI9341_BLACK); // erase value for new update
tft.fillRect(CX-25, CY+70, 30, 10, ILI9341_BLACK); // erase value for new update
tft.setCursor(CX-15, CY+80); // cursor position for odometer
tft.println("102");
tft.setCursor(CX+10, CY+80); // cursor position for odometer KM
tft.println("KM");
}
void drawOutline()
{
uint8_t x11=VX+8, y11=VY, x12=VX, y12=VY+5, x13=VX+3, y13=VY+5;
uint16_t x21=VX+8, y21=VY+5, x22=VX+3, y22=VY+5, x23=VX, y23=VY+10;
tft.setTextColor(ILI9341_WHITE);
// ESP Battery outline
tft.drawRoundRect(BATX, BATY, BATW, BATH, 1, ILI9341_WHITE); // battery outer boundary
tft.fillRoundRect(BATX+BATW, BATY+(BATH/3), 2, 5, 1, ILI9341_WHITE); // battery little rect at the right
tft.setCursor(BATX+(BATW/3), BATY-10);
tft.println("ESP");
// Motor Battery Outine
tft.drawRoundRect(BATX+BATW+10, BATY, BATW, BATH, 1, ILI9341_WHITE); // battery outer boundary
tft.fillRoundRect(BATX+BATW+10+BATW, BATY+(BATH/3), 2, 5, 1, ILI9341_WHITE); // battery little rect at the right
tft.setCursor(BATX+BATW+10+(BATW/3), BATY-10);
tft.println("Motor");
//Range icon 210 150
tft.drawRoundRect(RANX, RANY, 20, 30, 1, ILI9341_WHITE); // large box
tft.drawRoundRect(RANX+5, RANY+5, 10, 10, 1, ILI9341_WHITE); // small black box
tft.drawRoundRect(RANX-2, RANY+28, 25, 2, 1, ILI9341_WHITE); // bottom line
tft.drawRoundRect(RANX+23, RANY+5, 2, 25, 1, ILI9341_WHITE); // side line
// Temp icon
tft.fillRoundRect(RANX+71, RANY, 5, 30, 3, ILI9341_WHITE); // outer rect
tft.fillCircle(RANX+73, RANY+25, 5, ILI9341_WHITE); // outer white circle
tft.fillRoundRect(RANX+72, RANY+10, 3, 20, 3, ILI9341_RED); // inner red rect
tft.fillCircle(RANX+73, RANY+25, 4, ILI9341_RED); // inner red circle
// voltage symbol and text -> ESP
tft.fillTriangle(x11, y11, x12, y12, x13, y13, ILI9341_WHITE);
tft.fillTriangle(x21, y21, x22, y22, x23, y23, ILI9341_WHITE);
tft.setCursor(VX+15, VY+5);
tft.println("Volts");
// voltage symbol and text -> MOTOR
tft.fillTriangle(x11+60, y11, x12+60, y12, x13+60, y13, ILI9341_WHITE);
tft.fillTriangle(x21+60, y21, x22+60, y22, x23+60, y23, ILI9341_WHITE);
tft.setCursor(VX+15+60, VY+5);
tft.println("Volts");
}
void updateBatteryESP()
{
tft.fillRoundRect(BATX+1, BATY+1, BATW-2, BATH-2, 1, ILI9341_BLACK); // erase battery inside fill
// Raw value * ref_volt/adc_resolution * voltage divider factor
float BatVolts = data.BAT_ADC_ESP * (3.3/4095.0) * (6.3); // adjust the voltage divider factor
int BatPer = map(BatVolts * 1000, MIN_VOLTAGE * 1000, MAX_VOLTAGE * 1000, 0, 100);
if(BatPer < 0) BatPer = 0;
else if (BatPer > 100) BatPer = 100;
ESPfillW = (BatPer) / 2;
tft.fillRoundRect(BATX+1, BATY+1, ESPfillW-2, BATH-2, 1, ILI9341_GREEN); // battery inside fill
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(BATX+(BATW/3), BATY+(BATH/3));
tft.println(BatPer);
// Voltage text for ESP
tft.fillRect(VX+10, VY+25, 30, 10, ILI9341_BLACK); // erase value for new update
tft.setCursor(VX+5, VY+20); // cursor position for voltage value
tft.println(BatVolts);
tft.setCursor(VX+35, VY+20); // cursor position for ESP battery voltage V
tft.println("V");
if(data.ESPcharging)
{
tft.fillTriangle(BATX+(BATW-15)+3, BATY+2, BATX+(BATW-15)-3, BATY+8, BATX+(BATW-15)+1, BATY+8, ILI9341_YELLOW);
tft.fillTriangle(BATX+(BATW-15)+3, BATY+8, BATX+(BATW-15)-3, BATY+14, BATX+(BATW-15)-1, BATY+8, ILI9341_YELLOW);
}
}
void updateBatteryMOTOR()
{
tft.fillRoundRect(BATX+BATW+11, BATY+1, BATW-2, BATH-2, 1, ILI9341_BLACK); // erase battery inside fill
// Raw value * ref_volt/adc_resolution * voltage divider factor
float BatVolts = data.BAT_ADC_MOTOR * (3.3/4096) * (6.3); // adjust the voltage divider factor
int BatPer = map(BatVolts * 1000, MIN_VOLTAGE * 1000, MAX_VOLTAGE * 1000, 0, 100);
if(BatPer < 0) BatPer = 0;
else if (BatPer > 100) BatPer = 100;
MOTORfillW = BatPer / 2;
tft.fillRoundRect(BATX+BATW+11, BATY+1, MOTORfillW-2, BATH-2, 1, ILI9341_GREEN); // battery inside fill
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(BATX+BATW+10+(BATW/3), BATY+(BATH/3));
tft.println(BatPer);
// Voltage text for MOTOR
tft.fillRect(VX+60+10, VY+25, 30, 10, ILI9341_BLACK); // erase value for new update
tft.setCursor(VX+60+5, VY+20); // cursor position for voltage value
tft.println(BatVolts);
tft.setCursor(VX+95, VY+20); // cursor position for MOTOR battery voltage V
tft.println("V");
if(data.ESPcharging)
{
tft.fillTriangle(BATX+BATW+10+(BATW-15)+3, BATY+2, BATX+BATW+10+(BATW-15)-3, BATY+8, BATX+BATW+10+(BATW-15)+1, BATY+8, ILI9341_YELLOW);
tft.fillTriangle(BATX+BATW+10+(BATW-15)+3, BATY+8, BATX+BATW+10+(BATW-15)-3, BATY+14, BATX+BATW+10+(BATW-15)-1, BATY+8, ILI9341_YELLOW);
}
}
void drawGauge()
{
int ox,oy,ix,iy;
float rad;
for(int angle = STARTANGLE; angle <= STOPANGLE; angle += step) // main marking
{
rad = angle * (PI/180);
ox = (float)(CX + ((RADIUS-5) * cos(rad)));
oy = (float)(CY + ((RADIUS-5) * sin(rad)));
ix = (float)(CX + ((RADIUS-13) * cos(rad)));
iy = (float)(CY + ((RADIUS-13) * sin(rad)));
tft.drawLine(ox, oy, ix, iy, (angle<195?ILI9341_WHITE:(angle<=255?ILI9341_GREEN:(angle<315?ILI9341_ORANGE:ILI9341_RED))));
// < 140
ix = (float)(CX + ((angle<275?(RADIUS-20):(RADIUS-27)) * cos(rad)));
iy = (float)(CY + ((angle<275?(RADIUS-20):(RADIUS-27)) * sin(rad)));
tft.setTextColor(angle<315?ILI9341_WHITE:ILI9341_RED);
tft.setCursor(ix,iy);
tft.println(angle-135);
}
for(int angle = STARTANGLE+(step/2); angle <= STOPANGLE-(step/2); angle += step) // sub marking
{
rad = angle * (PI/180);
ox = (float)(CX + ((RADIUS-5) * cos(rad)));
oy = (float)(CY + ((RADIUS-5) * sin(rad)));
ix = (float)(CX + ((RADIUS-8) * cos(rad)));
iy = (float)(CY + ((RADIUS-8) * sin(rad)));
// adding 135 to speed gives the angle of the speed label on the gauge
// 0-60->white, 60-120->green, 120-180->orange, 180-260->red
tft.drawLine(ox, oy, ix, iy, (angle<195?ILI9341_WHITE:(angle<=255?ILI9341_GREEN:(angle<315?ILI9341_ORANGE:ILI9341_RED))));
}
tft.drawCircle(CX, CY, RADIUS+4, ILI9341_CYAN ); // outer ring
tft.drawCircle(CX, CY, RADIUS-60, ILI9341_ORANGE ); // center orange ring
tft.drawCircle(CX, CY, RADIUS, ILI9341_WHITE ); // main ring
tft.drawCircle(CX, CY, RADIUS-1, ILI9341_WHITE ); // main ring
tft.drawCircle(CX, CY, RADIUS-2, ILI9341_WHITE ); // main ring
tft.fillTriangle(100, 156, 0, 240, 200, 240, ILI9341_BLACK); // BLACK Triangle for odometer
}
void drawNeedle()
{
float angle = map(data.speed, 0, 260, STARTANGLE, STOPANGLE-(step/2)); // Map speed to angle (135 to 405 degrees)
float rad = angle * (PI/180);
tft.drawLine(nox, noy, nix, niy, ILI9341_BLACK); // erase the previous needle position
// calculate the needle co-ordinates
nox = (float)(CX + ((RADIUS-35) * cos(rad)));
noy = (float)(CY + ((RADIUS-35) * sin(rad)));
nix = (float)(CX + ((RADIUS-58) * cos(rad)));
niy = (float)(CY + ((RADIUS-58) * sin(rad)));
tft.drawLine(nox, noy, nix, niy, ILI9341_RED); // draw new needle position
tft.setTextColor(data.speed<180?ILI9341_WHITE:ILI9341_RED);
tft.fillCircle(CX, CY, RADIUS-62, ILI9341_BLACK ); //Inner-most black circle to display speed
tft.setTextSize(2);
tft.setCursor((data.speed<100?(CX-10):(CX-17)),(data.speed<100?(CY-13):(CY-16)));
tft.println(data.speed);
tft.setCursor(CX-10,CY+15);
tft.setTextSize(1);
tft.println("KMPH");
}