//GSI4.0 03/11/2024 - Initial release
//GSI4.01 19/10/2024 - Corrected boxer cup color scheme
//GSI4.02 20/01/2025 - Fan fix + program cleanup to free memory
//GSI4.03 05/05/2025 - Merging proto + prod programs
//GSI4.04 21/05/2025 - New way to enter setting loop
//GSI4.05 22/06/2025 - New way to enter setting loop
// Laurent "Buzzz" Buzzi - Copyright 2024
//#define DEBUG_INFO
//#define PROTO
#define FAN_ACTIVE
#include <EEPROM.h>
#include <RREFont.h>
#include "rre_gear.h"
#include "rre_bmwfont.h"
#include "rre_bmw.h"
#include "scale.h"
#include <Adafruit_ILI9341.h>
#include <Adafruit_GFX.h>
#ifdef PROTO
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 7
#define TFT_LED 8
#define FAN_PIN 1
const float light_mult = 1 ;
const uint8_t To_sensor = A0;
const uint8_t Vo_sensor = A7;
const uint8_t Gear_pins [] = {2, 3, 4};
const float Voltcoeff = 0.01735;
#else
#define TFT_CS 7
#define TFT_DC 9
#define TFT_RST 8
#define TFT_LED 10 //if you don't need to control the LED pin,you should set it to -1 and set it to 3.3V
#define FAN_PIN 2
const float light_mult = 1 ;
const uint8_t To_sensor = A1;
const uint8_t Vo_sensor = A7;
const uint8_t Gear_pins [] = {3, 4, 5};
const float Voltcoeff = 0.01760;
#endif
#define SCR_Siz 130
#define SCR_Max SCR_Siz-1
#define GEAR_X 41
#define GEAR_Y 42
#define GEAR_BIG_X 17
#define GEAR_BIG_Y 17
//#define ATEMP_X 180
//#define ATEMP_Y 215
#define OTEMP_X 94
#define OTEMP_Y 103
#define VOLT_X 92
#define VOLT_Y 10
#define DEG2RAD 0.0174532925
uint8_t celsius = 1;
uint8_t txt_color = 0;
const uint8_t roundeldiam[] = {53, 52, 27, 26};
const uint8_t tft_center = 65;
uint8_t light_lvl = 255;
const uint8_t fade_steps = 51;
const uint8_t fade_delay = 10;
bool blinker = LOW;
bool blinker_WAS = HIGH;
bool blink_ON = HIGH;
bool blink_CH = HIGH;
bool set_loop = LOW;
const uint8_t warnOtemp = 140;
uint16_t prevOtempVR = 0;
uint16_t OtempVR;
const float K = 273.15;
const uint16_t OtempB = 3600;
const float OtempVcoeff = 5./1024; //8184 for 13bits
const uint8_t OtempVref = 5;
const uint16_t OtempR2 = 1070;
float OtempR = 2200;
const uint16_t OtempRref = 2780;
const uint8_t OtempCref = 19;
float OtempV = 11.0;
int16_t OtempC = -31;
const uint8_t init_rot = 1;
uint8_t sens = 0;
const uint8_t cursor_offset = 3;
const uint8_t cursor_margin = 20;
const uint8_t graph_ymin = cursor_margin;
const uint8_t graph_ymax = SCR_Max - cursor_margin;
const uint8_t graphh = graph_ymax-graph_ymin;
const uint8_t bar_larg = 28;
const uint8_t temp_xmin = 103;
const uint8_t volt_min = 100;
const uint8_t volt_max = 170;
const uint8_t temp_min = 30;
const uint8_t temp_max = 150;
uint8_t index = 0;
uint8_t prev_volt_index = 64;
uint8_t prev_temp_index = 64;
const uint8_t FAN_temp_min = 115;
const uint8_t FAN_temp_max = 120;
const uint8_t FAN_Volt_min = 13.0;
const uint8_t FAN_Gear_max = 2;
const char degres [] = "<;";
uint16_t voltR;
uint16_t prevvoltR = 0;
float volt = 0.0;
uint8_t prevGear = 8;
uint8_t Gear = 8;
bool Gear_start = LOW;
bool Gear_bits[3];
char vitesse [1];
long last_gear_change = 0;
long last_setting_loop = 0;
long last_blink = 0;
long timer_gear = 0;
long timer_sensors = 0;
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define GREY 0x9999
#define DARKGREY 0x3186
const unsigned int blu_colors[] = {0x2bb3, 0x33d4, 0x33f4, 0x3c35, 0x4456, 0x4c77, 0x4c97, 0x54b8, 0x5cd8, 0x5cf9, 0x6519, 0x6539, 0x6d3a,};
const unsigned int txt_colors[3][9] = {{0xFFED,0xFFFF,0x9FED,0x77FA,0x6EDF,0xDE1F,0xFD34,0xFEED,0x0000},{0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0xFFFF},{0,0,0,0,0,0,0,0,1}};
RREFont font;
// needed for RREFont library initialization, define your fillRect
void customRect(int x, int y, int w, int h, int c) {return tft.fillRect(x, y, w, h, c); }
void setup()
{
#ifdef FAN_ACTIVE
pinMode(FAN_PIN, OUTPUT);
digitalWrite (FAN_PIN, HIGH);
delay(200);
digitalWrite (FAN_PIN, LOW);
#endif
pinMode(TFT_LED, OUTPUT);
analogWrite(TFT_LED, 0);
Read_gear();
// Gear = 0;
if (Gear > 4) {Gear_start = HIGH;}
// txt_color = 0;
#ifdef DEBUG_INFO
Serial.begin(115200);
Serial.println("DEBUG MODE ON");
#endif
for (uint8_t i = 0; i < 3; i++) pinMode(Gear_pins[i], INPUT);
pinMode(To_sensor, INPUT);
// pinMode(Ta_sensor, INPUT);
pinMode(Vo_sensor, INPUT);
// Serial.begin(9600);
EEPROM.get(0, celsius);
// Serial.println( celsius);
if (celsius != 0 && celsius != 1) {
celsius = 1;
EEPROM.put(0, celsius);
}
EEPROM.get( sizeof(int), txt_color );
// Serial.println( txt_color);
if (txt_color < 0 || txt_color > 8) {
txt_color = 0;
EEPROM.put( sizeof(int), txt_color );
}
// txt_color = ;
font.init(customRect, SCR_Siz, SCR_Siz);
tft.begin();
tft.fillScreen(BLACK);
// splashscreen();
if (Gear !=0 && Gear_start) Setting_loop();
}
void loop()
{
if (millis() - last_blink > 500)
{
blink_ON = !blink_ON;
blink_CH = HIGH;
last_blink = millis();
// Serial.print("blinker=");
// Serial.println(blinker);
}
if (millis() - timer_gear > 200)
{
Read_gear();
// Gear = 0;
// Gear ++;
// if (Gear == 8) Gear=0;
if (prevGear != Gear)
{
if (Gear != 7 ||(Gear == 7 && millis()-last_gear_change >1000)){
printGear();
// Serial.println(Gear);
prevGear = Gear;
last_gear_change = millis();
}
}
timer_gear = millis();
}
if (millis() - timer_sensors > 1000)
{
Read_sensors();
#ifdef DEBUG_INFO
// Serial.print(Gear);
// Serial.print(",");
// Serial.print(volt);
// Serial.print(",");
// Serial.println(OtempC);
#endif
timer_sensors = millis();
}
printOTemp();
printvolt();
//delay(100);
}
void draw_cursor(int pos_x, int sens, int valeur, int mini, int maxi , int previndex)
{
index = SCR_Max - constrain(map( valeur, mini, maxi, graph_ymin, graph_ymax), graph_ymin, graph_ymax);
if (index != previndex )
{
draw_scale(pos_x, SCR_Max, sens, SCR_Max-previndex - 8 , SCR_Max - previndex + 8);
tft.fillTriangle(pos_x + (cursor_offset -3) * sens , index, pos_x + (bar_larg + cursor_offset -4) * sens , index - 8, pos_x + (bar_larg + cursor_offset -4) * sens, index + 8, txt_colors[1][txt_color]);
tft.fillTriangle(pos_x + cursor_offset * sens , index, pos_x + (bar_larg + cursor_offset) * sens , index , pos_x + (bar_larg + cursor_offset) * sens, index - 8, txt_colors[0][txt_color]);
tft.fillTriangle(pos_x + cursor_offset * sens , index+1, pos_x + (bar_larg + cursor_offset) * sens , index + 1, pos_x + (bar_larg + cursor_offset) * sens, index +8, txt_colors[0][txt_color] - DARKGREY);
}
}
void draw_scale(int x, int y, int sens, int mini, int maxi) {
for (uint16_t i = mini; i < maxi ; i++) {
if ( i % 10 == 0) {tft.writeFastHLine(x , y - i , bar_larg * sens , BLACK);}
else {tft.writeFastHLine(x , y - i , bar_larg * sens , BLACK);}
}
}
void splashscreen() {
Read_sensors();
analogWrite(TFT_LED, 0);
// Roundel
font.setColor(WHITE);
// BMW characters
tft.setRotation(init_rot + 2);
font.setFont(&rre_bmw);
font.setScale(1);
font.setSpacing(0);
font.printStr(26, 29,(char*)"0");
font.printStr(57, 15,(char*)"1");
font.printStr(86, 27,(char*)"2");
for (uint8_t i = 0; i < 12; i++) {
tft.fillCircle(tft_center+1, tft_center, roundeldiam[2] - i * 4, blu_colors[i]);
}
font.setFont(&rre_helice);
font.printStr(tft_center-23, tft_center+1, "0");
tft.setRotation(init_rot + 1);
font.printStr(tft_center-25, tft_center+1, "0");
// White circles
for (uint8_t i = 0; i < 4; i++) {
tft.drawCircle(tft_center+1, tft_center, roundeldiam[i], WHITE);
tft.drawCircle(tft_center, tft_center, roundeldiam[i], WHITE);
}
fade (0,5);
delay(1000);
fade(255,-5);
tft.fillScreen(BLACK);
delay(500);
// tft.drawCircle(tft_center, tft_center, 64, WHITE);
Read_sensors();
// R1100S logo
tft.setRotation(init_rot + 2);
font.setFont(&rre_R1100S);
font.setScale(1);
font.setSpacing(0);
font.printStr(10, 55, "01234");
fade (0,5);
delay(1000);
fade(255,-5);
tft.fillScreen(BLACK);
delay(500);
tft.setRotation(init_rot + 2);
printlines();
draw_scale (0, SCR_Max, 1, 0, SCR_Max);
draw_scale (SCR_Siz - bar_larg + 1, SCR_Max, 1, 0, SCR_Max);
printOTemp();
printvolt();
Read_gear();
printGear();
delay(500);
fade (0,5);
}
void Read_gear()
{
for (uint8_t i = 0; i < 3; i++) Gear_bits[i] = digitalRead(Gear_pins[i]);
Gear = Gear_bits[0] + Gear_bits[1] * 2 + Gear_bits[2] * 4;
}
void Read_sensors()
{
// Serial.print("sensor: ");
// Serial.println(timer_sensors);
uint16_t alpha;
if (sens == 0){
voltR = analogRead(Vo_sensor);
delay(100);
voltR = analogRead(Vo_sensor);
delay(100);
alpha = analogRead(To_sensor);
// volt += 13.2;
// Serial.print("voltR : "); Serial.println(voltR);
}
else
{
OtempVR = analogRead(To_sensor);
delay(100);
OtempVR = analogRead(To_sensor);
delay(100);
alpha = analogRead(Vo_sensor);
}
sens = 1 - sens;
}
void printGear()
{
font.setFont(&rre_gear);
font.setBold(1);
font.setScale(1);
font.setCharMinWd(48);
font.setSpacing(1);
if (Gear == 0) {
font.setColor(BLACK,GREEN);
tft.fillRect(31, 37, 68, 58, GREEN);
// tft.fillCircle(tft_center+1, tft_center, 33, GREEN);
}
else {
font.setColor(txt_colors[0][txt_color], txt_colors[1][txt_color]);
tft.fillRect(31, 37, 68, 58, txt_colors[1][txt_color]);
}
dtostrf(Gear, 0, 0, vitesse);
font.printStr(GEAR_X, GEAR_Y, vitesse);
}
void printvolt()
{
if (abs(voltR - prevvoltR) >= 1)
{
volt = voltR * Voltcoeff; // + 16 - light_read / 20;
char volte [10];
font.setFont(&rre_bmwfont);
font.setBold(0);
font.setScale(1);
font.setCharMinWd(5);
font.setSpacing(2);
font.setColor(txt_colors[0][txt_color], txt_colors[1][txt_color]);
dtostrf(volt, 3, 1, volte);
strcat (volte, ":");
tft.fillRect(28, 0, 71, VOLT_Y+23, txt_colors[1][txt_color]);
font.printStr(VOLT_X - font.strWidth(volte), VOLT_Y, volte);
//DEV
// draw_cursor (bar_larg , -1, volt * 10 , volt_min , volt_max, prev_volt_index);
prev_volt_index = index;
prevvoltR = voltR;
}
}
void printOTemp()
{
// Serial.print(blink_ON);Serial.print(",");Serial.println(blink_CH);
if ((blinker && blink_CH) || blinker_WAS || set_loop) prevOtempVR = OtempVR - 2;
if (abs(OtempVR - prevOtempVR)>= 1)
{
if (OtempVR != 0){
OtempR= OtempR2/((float) OtempVref/OtempVR/OtempVcoeff-1);
OtempC=(OtempCref+K)/(1-(OtempCref+K)/OtempB*log(OtempRref/OtempR))-K;
}
// OtempC = 70;
if (OtempC >= warnOtemp) blinker = HIGH;
else
{
if (blinker)
{
blinker = LOW;
blinker_WAS = HIGH;
}
}
int16_t OtempF = OtempC;
char tempe[10];
font.setFont(&rre_bmwfont);
font.setBold(0);
font.setScale(1);
font.setCharMinWd(5);
font.setSpacing(2);
// Serial.print("celsius=");
// Serial.println(celsius);
if (celsius > 1) celsius = 1;
if (celsius == 0) OtempF = OtempF * 1.8 + 32;
dtostrf(OtempF, 3, 0, tempe);
strcat (tempe, "/");
strncat (tempe, °res[celsius], 1);
// Serial.println(degres);
// Serial.println(tempe);
if (blinker && blink_ON) font.setColor(txt_colors[1][txt_color], txt_colors[1][txt_color]);
else font.setColor(txt_colors[0][txt_color], txt_colors[1][txt_color]);
tft.fillRect(31, OTEMP_Y-4, 70, 31,txt_colors[1][txt_color]);
font.printStr(OTEMP_X - font.strWidth(tempe), OTEMP_Y, tempe);
//DEV
// draw_cursor (SCR_Siz-bar_larg + 1 , 1, OtempC, temp_min, temp_max, prev_temp_index);
prev_temp_index = index;
if (!blinker) blinker_WAS = LOW;
#ifdef FAN_ACTIVE
if ((OtempC >= FAN_temp_max) && (Gear <= FAN_Gear_max) && (volt >= FAN_Volt_min))
{
// #ifdef DEBUG_INFO
// Serial.print(FAN_temp_max);
// Serial.print(",");
// Serial.println(OtempC);
// #endif
digitalWrite (FAN_PIN, HIGH);
}
else if ((OtempC <= FAN_temp_min) || (Gear > FAN_Gear_max) || (volt < FAN_Volt_min)) digitalWrite (FAN_PIN, LOW);
#endif
}
// font.printStr(OTEMP_X - font.strWidth(tempe), OTEMP_Y, ";");
// Serial.println(OTEMP_X - font.strWidth(tempe));
blink_CH = LOW;
prevOtempVR = OtempVR;
}
void printlines()
{
for (uint8_t i = 0; i < 3; i++) tft.drawRect(28+i, 34+i, 102-i, 98-i, txt_colors[0][txt_color]);
tft.fillRect(28, tft_center, 30, SCR_Siz, txt_colors[0][txt_color]);
tft.fillRect(100, 0, 102, tft_center, txt_colors[0][txt_color]);
}
void Setting_loop()
{
set_loop=HIGH;
uint8_t Gear_set = Gear;
uint8_t celsius_init = celsius;
uint8_t txt_color_init = txt_color;
printGear();
printOTemp();
printvolt();
while (Gear == Gear_set)
// Serial.println(txt_color);
{
if (millis() - last_setting_loop >= 2000)
{
if (Gear_set == 6) celsius = 1 - celsius;
if (Gear_set == 5)
{
txt_color += 1;
if (txt_color == 9 ) txt_color = 0;
// Serial.println(txt_color);
tft.fillScreen(txt_colors[1][txt_color]);
// Serial.println(txt_color);
}
printlines();
printGear();
printOTemp();
printvolt();
//DEV
// draw_scale (0, SCR_Max, 1, 0, SCR_Max);
// draw_scale (SCR_Siz - bar_larg + 1, SCR_Max, 1, 0, SCR_Max);
// draw_cursor (SCR_Siz-bar_larg + 1 , 1, OtempC, temp_min, temp_max, prev_temp_index);
// draw_cursor (bar_larg , -1, volt , volt_min , volt_max, prev_volt_index);
last_setting_loop = millis();
}
Read_gear();
// Gear=2;
delay (100);
}
if (celsius != celsius_init) EEPROM.update(0, celsius);
if (txt_color != txt_color_init) EEPROM.update(sizeof(int), txt_color);
set_loop=LOW;
}
void fade (uint8_t debut, int8_t fade_step) {
uint8_t light_lvl = debut;
uint8_t i = 0;
while (i < fade_steps)
{
analogWrite(TFT_LED, light_lvl* light_mult);
light_lvl += fade_step;
delay(fade_delay);
i += 1;
// Serial.println(light_lvl);
}
}