#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <OneWire.h>
#include <DallasTemperature.h>
// === TFT ===
#define TFT_DC 9
#define TFT_CS 10
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
// === DS18B20 ===
#define ONE_WIRE_BUS 2 // DS18B20 on pin 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
// === UI Variables ===
int textX1 = 110;
int textY1 = 120;
int textX2 = 58;
int textY2 = 220;
int textX3 = 58;
int textY3 = 300;
int cx = 120;
int cy = 160;
int cx2 = 60;
int cy2 = 240;
int cx3 = 60;
int cy3 = 318;
int r = 100;
int r2 = 50;
int maxLimitedSpeed = 40;
float maxSpeed = 130;
int maxTemp = 120; // scale for gauge
int maxVolt = 42; // scale for gauge
int oldAngle = 0;
int oldTempAngle = 0;
int oldVoltAngle = 0;
int textWidth = 50;
int textHeight = 20;
int position = 0;
bool tempErrorPrev = false;
bool overchargeShown = false;
bool lowShown = false;
bool criticalShown = false;
bool overSpeedShown = false;
bool limiterOffShown = false;
void setup() {
Serial.begin(9600);
tft.begin();
sensors.begin(); // init DS18B20
tft.fillScreen(ILI9341_BLACK);
// Speedometer semicircle
refreshSemi();
console("init complete", ILI9341_GREEN);
}
void loop() {
tft.fillRect(0, 179, 240, 3, ILI9341_BLUE);
tft.fillRect(119, 179, 3, 180, ILI9341_BLUE);
tft.fillRect(textX1, textY1, textWidth, textHeight, ILI9341_BLACK);
tft.fillRect(textX2, textY2, 25, 7, ILI9341_BLACK);
tft.fillRect(textX3, textY3, 30, 7, ILI9341_BLACK);
// Read DS18B20 temperature
// Read DS18B20 temperature
sensors.requestTemperatures();
int temp = sensors.getTempCByIndex(0); // first sensor
bool tempErrorNow = (temp == DEVICE_DISCONNECTED_C);
if (tempErrorNow && !tempErrorPrev) {
console("Temp sensor dead", ILI9341_RED);
Serial.println("Logged DS18B20 error");
}
// Reset flag if sensor comes back
if (!tempErrorNow && tempErrorPrev) {
console("Temp OK", ILI9341_YELLOW);
Serial.println("Sensor reconnected");
}
tempErrorPrev = tempErrorNow; // update state
int toggle = digitalRead(7);
int pot = analogRead(A0);
int speed = map(pot, 0, 1023, 0, maxSpeed);
int rawVolt = analogRead(A1);
float volt = map(rawVolt, 0, 1023, 0, maxVolt);
int n = random(0, 3);
if (volt > maxVolt) {
if (!overchargeShown) {
console("Battery Overcharged", ILI9341_RED);
overchargeShown = true;
}
} else {
overchargeShown = false; // reset if voltage normal again
}
if (volt <= 35 && volt > 33) {
if (!lowShown) {
console("Battery Low", ILI9341_YELLOW);
lowShown = true;
criticalShown = false; // prevent both showing
}
} else if (volt <= 33) {
if (!criticalShown) {
console("Battery Critical", ILI9341_RED);
criticalShown = true;
lowShown = false; // prevent both showing
}
} else {
// reset if voltage normal again
lowShown = false;
criticalShown = false;
}
if (speed > 25 && toggle == 0) {
if (!overSpeedShown) {
console("dude, slow down", ILI9341_RED);
overSpeedShown = true;
}
} else {
overSpeedShown = false; // reset if voltage normal again
}
if (toggle == 1) {
if (!limiterOffShown) {
console("speed limit off.", ILI9341_YELLOW);
pula(n);
limiterOffShown = true;
}
} else {
limiterOffShown = false; // reset if voltage normal again
}
if (speed <= 9) {
textX1 = 115;
} else if (speed <= 99) {
textX1 = 110;
} else {
textX1 = 103;
}
if (volt <= 9) {
textX3 = 52;
} else if (volt <= 99) {
textX3 = 46;
} else {
textX3 = 39;
}
Serial.println(n);
// Update gauges
updateTNeedle(temp, maxTemp);
updateVNeedle(volt, maxVolt);
tft.setCursor(textX1, textY1);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.print(speed);
tft.setCursor(112, 140);
tft.setTextSize(1);
tft.print("kph");
if (!tempErrorNow) {
tft.setCursor(textX2, textY2);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.print(temp);
} else {
tft.setCursor(textX2, textY2);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.print("N/A");
}
tft.setCursor(58, 230);
tft.setTextSize(1);
tft.print("C");
tft.setCursor(textX3, textY3);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.print(volt);
tft.setCursor(58, 308);
tft.setTextSize(1);
tft.print("V");
if (toggle == HIGH) {
updateSNeedle(speed, maxSpeed);
} else {
updateLimitedSNeedle(speed, maxLimitedSpeed);
}
millis(50);
}
void updateSNeedle(int speed, int maxSpeed) {
int angle = map(speed, 0, maxSpeed, 0, 180);
float radOld = oldAngle * PI / 180;
float radNew = angle * PI / 180;
int oldX = cx - (r - 15) * cos(radOld);
int oldY = cy - (r - 15) * sin(radOld);
int newX = cx - (r - 15) * cos(radNew);
int newY = cy - (r - 15) * sin(radNew);
tft.drawLine(cx, cy, oldX, oldY, ILI9341_BLACK);
tft.drawLine(cx, cy, newX, newY, ILI9341_RED);
oldAngle = angle;
}
void updateLimitedSNeedle(int speed, int maxSpeed) {
int angle = map(speed, 0, maxSpeed, 0, 180);
float radOld = oldAngle * PI / 180;
float radNew = angle * PI / 180;
int oldX = cx - (r - 15) * cos(radOld);
int oldY = cy - (r - 15) * sin(radOld);
int newX = cx - (r - 15) * cos(radNew);
int newY = cy - (r - 15) * sin(radNew);
tft.drawLine(cx, cy, oldX, oldY, ILI9341_BLACK);
tft.drawLine(cx, cy, newX, newY, ILI9341_WHITE);
oldAngle = angle;
}
void updateTNeedle(float temp, int maxTemp) {
int angle = map(temp, 0, maxTemp, 0, 180);
float radOld = oldTempAngle * PI / 180;
float radNew = angle * PI / 180;
int oldX = cx2 - (r2 - 15) * cos(radOld);
int oldY = cy2 - (r2 - 15) * sin(radOld);
int newX = cx2 - (r2 - 15) * cos(radNew);
int newY = cy2 - (r2 - 15) * sin(radNew);
tft.drawLine(cx2, cy2, oldX, oldY, ILI9341_BLACK);
tft.drawLine(cx2, cy2, newX, newY, ILI9341_YELLOW);
oldTempAngle = angle;
}
void updateVNeedle(float volt, int maxVolt) {
int angle = map(volt, 0, maxVolt, 0, 180);
float radOld = oldVoltAngle * PI / 180;
float radNew = angle * PI / 180;
int oldX = cx3 - (r2 - 15) * cos(radOld);
int oldY = cy3 - (r2 - 15) * sin(radOld);
int newX = cx3 - (r2 - 15) * cos(radNew);
int newY = cy3 - (r2 - 15) * sin(radNew);
tft.drawLine(cx3, cy3, oldX, oldY, ILI9341_BLACK);
tft.drawLine(cx3, cy3, newX, newY, ILI9341_YELLOW);
oldVoltAngle = angle;
}
void console(String errorMessage, uint16_t color) {
switch (position) {
case 0:
tft.fillRect(125, 185, 240, 8, ILI9341_BLACK);
tft.setCursor(125, 185);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 1:
tft.setCursor(125, 194);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 2:
tft.setCursor(125, 203);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 3:
tft.setCursor(125, 212);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 4:
tft.setCursor(125, 221);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 5:
tft.setCursor(125, 230);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 6:
tft.setCursor(125, 239);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 7:
tft.setCursor(125, 248);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 8:
tft.setCursor(125, 257);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 9:
tft.setCursor(125, 266);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 10:
tft.setCursor(125, 275);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 11:
tft.setCursor(125, 284);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 12:
tft.setCursor(125, 293);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 13:
tft.setCursor(125, 302);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position += 1;
break;
case 14:
tft.setCursor(125, 311);
tft.setTextColor(color);
tft.setTextSize(1);
tft.print(errorMessage);
Serial.println("logged");
position = 0;
break;
default:
//nothing.
}
}
void refreshSemi() {
for (int i = 0; i <= 180; i++) {
float rad = i * PI / 180;
int x0 = cx + r * cos(rad);
int y0 = cy - r * sin(rad);
tft.drawPixel(x0, y0, ILI9341_ORANGE);
}
for (int i = 0; i <= 180; i += 20) {
float rad = i * PI / 180;
int x0 = cx + (r - 10) * cos(rad);
int y0 = cy - (r - 10) * sin(rad);
int x1 = cx + r * cos(rad);
int y1 = cy - r * sin(rad);
tft.drawLine(x0, y0, x1, y1, ILI9341_ORANGE);
}
// Temp semicircle
for (int i = 0; i <= 180; i++) {
float rad = i * PI / 180;
int x0 = cx2 + r2 * cos(rad);
int y0 = cy2 - r2 * sin(rad);
tft.drawPixel(x0, y0, ILI9341_ORANGE);
}
for (int i = 0; i <= 180; i += 20) {
float rad = i * PI / 180;
int x0 = cx2 + (r2 - 10) * cos(rad);
int y0 = cy2 - (r2 - 10) * sin(rad);
int x1 = cx2 + r2 * cos(rad);
int y1 = cy2 - r2 * sin(rad);
tft.drawLine(x0, y0, x1, y1, ILI9341_ORANGE);
}
// Volt semicircle
for (int i = 0; i <= 180; i++) {
float rad = i * PI / 180;
int x0 = cx3 + r2 * cos(rad);
int y0 = cy3 - r2 * sin(rad);
tft.drawPixel(x0, y0, ILI9341_ORANGE);
}
for (int i = 0; i <= 180; i += 20) {
float rad = i * PI / 180;
int x0 = cx3 + (r2 - 10) * cos(rad);
int y0 = cy3 - (r2 - 10) * sin(rad);
int x1 = cx3 + r2 * cos(rad);
int y1 = cy3 - r2 * sin(rad);
tft.drawLine(x0, y0, x1, y1, ILI9341_ORANGE);
}
}
void pula(int n) {
switch (n) {
case 0:
console("MIKU MIKU BEAM>:)", ILI9341_CYAN);
break;
case 1:
console("watch out for cops", ILI9341_GREEN);
break;
case 2:
console("VROOMY VROOM>:3", ILI9341_GREEN);
break;
default:
console("*insert initial d*", ILI9341_GREEN);
}
}