500 lines
19 KiB
C++
500 lines
19 KiB
C++
/*========= Va&Cob OBD2 Gauge ==========
|
|
Programmed: by Ratthanin W.
|
|
Hardware:
|
|
- Hardware 2.8" TFT ESP32 LVGL https://www.youtube.com/watch?v=d2OXlVcRYrU
|
|
Configuration:
|
|
- Partition scheme
|
|
Minimal SPIFFS 1.9MB APP/ 190kB SPIFFS /OTA (for OTA ,must reduce code size)
|
|
|
|
Important Note:
|
|
- New device must flash "gauge_factory_init.ino" first to get it work by checking 'serialno' in pref.
|
|
or you can skip genuine checking by comment out line no. 338
|
|
- CPU temp read never work until Bluetooth or Wifi is connected
|
|
|
|
About opening logo image and goodbye image.
|
|
- you can save welcome page logo into gauge by saving file name "vaandcob.jpg" size 320x240 pixels into littleFS storage.
|
|
- you can save goodbye page image into gauge by saving file name "mypic.jpg" size 320z240 pixels into littleFS storage
|
|
* if you don't want to use it, you can comment out code line no. 320 - 322
|
|
*/
|
|
|
|
//--- FLAG SETTING ----- for debugging
|
|
//#define TERMINAL //temrinal mode (no gauge)
|
|
//#define SERIAL_DEBUG //to show data in serial port
|
|
//#define SKIP_CONNECTION //skip elm327 BT connection to view meter
|
|
//#define TEST_DTC //test DTC
|
|
|
|
//#define FORD_T5 // FOR FORD T5 uncomment this line
|
|
|
|
//Intermal temperature sensor function declaration
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
uint8_t temprature_sens_read();
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
uint8_t temprature_sens_read(); //declare intermal temperature sensor functio
|
|
|
|
//Macro
|
|
#define array_length(x) (sizeof(x) / sizeof(x[0])) //macro to calculate array length
|
|
|
|
//library load
|
|
#include <Preferences.h> //save permanent data
|
|
Preferences pref; //create preference
|
|
#include <BluetoothSerial.h>
|
|
#include <TFT_eSPI.h> //Hardware-specific library
|
|
|
|
|
|
//Pin configuration
|
|
#define SCA_PIN 21 //not used same pin with TFT_BL
|
|
#define SCL_PIN 22 //not used (avaialable) CAN_TX
|
|
#define ADC_PIN 35 //not used (available) CAN_RX
|
|
#define LED_RED_PIN 4
|
|
#define LED_GREEN_PIN 16
|
|
#define LED_BLUE_PIN 17
|
|
#define LDR_PIN 34 //LDR sensor
|
|
#define BUZZER_PIN 26 //speaker
|
|
#define SELECTOR_PIN 27 //push button
|
|
// setting PWM properties
|
|
#define backlightChannel 0
|
|
#define buzzerChannel 2
|
|
|
|
//DISPLAY
|
|
TFT_eSPI tft = TFT_eSPI();
|
|
#define TFT_GREY 0x5AEB
|
|
|
|
|
|
//ELM327 init https://www.elmelectronics.com/wp-content/uploads/2016/07/ELM327DS.pdf
|
|
const uint8_t elm327InitCount = 8;
|
|
const String elm327Init[elm327InitCount] = { "ATZ", "ATBRD23", "ATSP0", "ATAT2", "ATL0", "ATH0", "ATE0", "0100" }; //agressive wait response time from ECU
|
|
//const String elm327Init[elm327InitCount] = {"ATZ","ATPP0CSV35","ATPP0CON","ATSP0","ATAT2","ATL0","ATH0","ATE0","0100"};//agressive wait response time from ECU
|
|
|
|
/*------ PIDS total 7 now -------------------
|
|
Array data -> { Label , unit, pid, fomula, min, max, ,skip, digit, warn }
|
|
label = Pid label show on meter
|
|
unit = unit for pid
|
|
pid = string pid 0104
|
|
formula = formula no to calculate
|
|
min = lowest value
|
|
max = highest value
|
|
skip = delay reading 0 - 3; 3 = max delay read
|
|
digit = want to show digit or not
|
|
warn = default warning value
|
|
*/
|
|
const String pidConfig[4][9] = {
|
|
//[pid][data]
|
|
{ "Temperatura", "`C", "0105", "1", "0", "120", "2", "0", "106" }, //0 = Temperatura (0105)
|
|
{ "RPM", "rpm", "010C", "3", "0", "6000", "0", "0", "5000" }, //1 = RPM (010C)
|
|
{ "Voltaje", "V", "0142", "4", "10", "16", "1", "1", "15" }, //2 = Voltaje (0142)
|
|
{ "Velocidad", "km/h", "010D", "7", "0", "160", "0", "0", "130" } //3 = Velocidad (010D)
|
|
};
|
|
|
|
//hold warning value
|
|
String warningValue[4] = { "106", "5000", "15", "130" };
|
|
|
|
/*
|
|
0 - Refrigerante
|
|
1 - Revoluciones
|
|
2 - Voltaje
|
|
3 - Velocidad
|
|
*/
|
|
const uint8_t pidInCell[4] = { 0, 3, 1, 2 }; // 4 Cuadrantes: 0=Refrigerante, 1=Velocidad, 2=Revoluciones, 3=Voltaje
|
|
|
|
bool cellIsNumeric[4] = { false, false, false, false }; // false = Reloj (Arco), true = Solo Numero
|
|
int8_t fullScreenCell = -1; // -1 = 4 cuadrantes, 0..3 = pantalla completa de ese cuadrante
|
|
// User configuration here to change display >
|
|
|
|
/*---------------------------*/
|
|
//if NO DATA value will set to 0 to skip reading
|
|
uint8_t layout = 0; //pidInCelltype 0-4 EEPROM 0x00
|
|
const uint8_t max_layout = 1; //single screen mode
|
|
uint8_t pidIndex = 0; //point to PID List
|
|
const uint8_t maxpidIndex = array_length(pidConfig); //total 7 pids in picConfig
|
|
bool prompt = false; //bt elm ready flag
|
|
bool skip = false; //pid skip flag
|
|
uint8_t A = 0; //get A
|
|
uint8_t B = 0; //get B
|
|
|
|
//Global variable
|
|
const uint32_t SLEEP_DURATION = 180 * 1000000; //180 sec* us = 3 min
|
|
String line[12] = { "", "", "", "", "", "", "", "", "", "", "", "" }; //buffer for each line in terminal function
|
|
bool dim = false; //back light dim flag
|
|
const uint8_t LIGHT_LEVEL = 40; //backlight control by light level Higher = darker light
|
|
String serial_no = ""; //keep serial no.
|
|
|
|
|
|
//BLUETOOTH
|
|
String bt_message = ""; //bluetooth message buffer
|
|
String se_message = ""; //serial port message buffer
|
|
String deviceName[8] = { "", "", "", "", "", "", "", "" }; //discoveryBT name
|
|
String deviceAddr[8] = { "", "", "", "", "", "", "", "" }; //discover BT addr
|
|
uint8_t btDeviceCount = 0; //discovered bluetooth devices counter
|
|
#define BT_DISCOVER_TIME 5000 //bluetooth discoery time
|
|
esp_bd_addr_t client_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //obdII mac addr
|
|
esp_bd_addr_t recent_client_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; //keep last btaddr in RTC memory
|
|
const String client_name = "OBDII"; //adaptor name to search
|
|
esp_spp_sec_t sec_mask = ESP_SPP_SEC_NONE; // or ESP_SPP_SEC_ENCRYPT|ESP_SPP_SEC_AUTHENTICATE to request pincode confirmation
|
|
esp_spp_role_t role = ESP_SPP_ROLE_SLAVE; // or ESP_SPP_ROLE_MASTER
|
|
bool foundOBD2 = false;
|
|
BluetoothSerial BTSerial; //bluetooth serial device
|
|
|
|
//---- Include Header File ---
|
|
#include "image.h"
|
|
#include "touchscreen.h"
|
|
|
|
SoftSPI *tsSoftSpi = nullptr;
|
|
XPT2046_TouchscreenSOFTSPI ts(XPT2046_CS);
|
|
|
|
/*----------- global function ---------*/
|
|
|
|
|
|
/*------------------*/
|
|
//TERMINAL terminal for debugging display text
|
|
void Terminal(String texts, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
|
uint8_t max_line = round((h - y) / 16.0);
|
|
tft.fillRect(x, y, x + w, y + h, TFT_BLACK);
|
|
tft.setTextColor(TFT_WHITE);
|
|
for (uint8_t i = 0; i < max_line; i++) {
|
|
if (i <= max_line - 2) {
|
|
line[i] = line[i + 1];
|
|
} else {
|
|
line[i] = texts;
|
|
}
|
|
tft.drawString(line[i], x, i * 20 + y, 2);
|
|
} //for
|
|
bt_message = ""; //reset bt message
|
|
}
|
|
/*------------------*/
|
|
|
|
void checkGenuine() { //check if genuine obd2 gauge - must flash "gauge_factory_init.ino" first
|
|
//security {serialno: "V&C-OBDII-001"}
|
|
pref.begin("security", true); //read only
|
|
serial_no = pref.getString("serialno", ""); //if no serial no, then empty
|
|
Serial.print(F("Serial No: "));
|
|
if (serial_no == "") {
|
|
tft.setTextColor(TFT_WHITE, TFT_RED);
|
|
tft.drawString("Not genuine Va&Cob Gauge!", 0, 115, 4);
|
|
Serial.println(F("Not genuine Va&Cob Gauge!"));
|
|
while (true) { //beep
|
|
ledcWriteTone(buzzerChannel, 2000);
|
|
digitalWrite(LED_RED_PIN, LOW); //red on
|
|
delay(500);
|
|
ledcWriteTone(buzzerChannel, 0);
|
|
digitalWrite(LED_RED_PIN, HIGH); //red off
|
|
delay(500);
|
|
}
|
|
} else {
|
|
Serial.println(serial_no); //print serial no
|
|
pref.end();
|
|
}
|
|
}
|
|
//----------------------------------------------
|
|
|
|
//---- Include Header File --------------------
|
|
//#include "image.h"
|
|
#include "bluetooth.h"
|
|
#include "meter.h"
|
|
#include "config.h"
|
|
|
|
//----SET UP -----------------------------------
|
|
void setup() {
|
|
//pin configuration
|
|
analogSetAttenuation(ADC_0db); // 0dB(1.0 ครั้ง) 0~800mV for LDR
|
|
pinMode(LDR_PIN, ANALOG); //ldr analog input read brightness
|
|
pinMode(BUZZER_PIN, OUTPUT); //speaker
|
|
pinMode(SELECTOR_PIN, INPUT_PULLUP); //button
|
|
pinMode(LED_RED_PIN, OUTPUT); //red
|
|
pinMode(LED_GREEN_PIN, OUTPUT); //green
|
|
pinMode(LED_BLUE_PIN, OUTPUT); //blue
|
|
digitalWrite(LED_RED_PIN, LOW); //on
|
|
digitalWrite(LED_GREEN_PIN, HIGH); //off
|
|
digitalWrite(LED_BLUE_PIN, HIGH); //off
|
|
//pwm setup
|
|
ledcSetup(buzzerChannel, 1500, 10); //buzzer 10 bit
|
|
ledcSetup(backlightChannel, 12000, 8); //backlight 8 bit
|
|
ledcAttachPin(BUZZER_PIN, buzzerChannel); //attach buzzer
|
|
|
|
//init communication
|
|
Serial.begin(115200);
|
|
//memory check
|
|
psramInit();
|
|
Serial.println(F("\n---------------------------------------\n"));
|
|
Serial.printf("Free Internal Heap %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize());
|
|
Serial.printf("Free SPIRam Heap %d/%d\n", ESP.getFreePsram(), ESP.getPsramSize());
|
|
Serial.printf("ChipRevision %d, Cpu Freq %d, SDK Version %s\n", ESP.getChipRevision(), ESP.getCpuFreqMHz(), ESP.getSdkVersion());
|
|
Serial.printf("Flash Size %d, Flash Speed %d\n", ESP.getFlashChipSize(), ESP.getFlashChipSpeed());
|
|
Serial.println(F("\n---------------------------------------\n"));
|
|
Serial.println(F("<< Va&Cob OBDII Gauge >>"));
|
|
|
|
|
|
|
|
//init TFT display
|
|
tft.init();
|
|
tft.setRotation(1); //landcape
|
|
// Start SoftSPI for the touchscreen and init the touchscreen
|
|
tsSoftSpi = new SoftSPI(XPT2046_MOSI, XPT2046_MISO, XPT2046_CLK);
|
|
ts.begin(tsSoftSpi);
|
|
ts.setRotation(1);
|
|
|
|
touch_calibrate(); // initialize / load touch calibration
|
|
//testTouch();//test touchscreen
|
|
|
|
tft.setSwapBytes(true); // to display correct image colors
|
|
|
|
// Eliminar cualquier imagen splash de inicio/bluetooth de SPIFFS
|
|
if (SPIFFS.begin(true)) {
|
|
if (SPIFFS.exists("/vaandcob.jpg")) {
|
|
SPIFFS.remove("/vaandcob.jpg");
|
|
}
|
|
SPIFFS.end();
|
|
}
|
|
|
|
//backlight ledcAttachPin must be set after tft.init()
|
|
ledcAttachPin(TFT_BL, backlightChannel); //attach backlight
|
|
for (uint8_t i = 255; i > 0; i--) { //fading effect
|
|
ledcWrite(backlightChannel, i); //full bright
|
|
delay(5);
|
|
}
|
|
digitalWrite(LED_RED_PIN, HIGH); //red off
|
|
tft.fillScreen(TFT_BLACK); //show start page
|
|
tft.fillRectVGradient(0, 0, 320, 26, 0x2104, TFT_BLACK);
|
|
tft.setTextColor(TFT_WHITE);
|
|
tft.drawString("OBDII GAUGE", 10, 5, 2);
|
|
ledcWrite(backlightChannel, 255); //full bright
|
|
|
|
// checkGenuine();//check guniune
|
|
//init variable
|
|
pref.begin("setting", false);
|
|
/* Create a namespace called "setting" with read/write mode
|
|
{ pref.get...(key,default)
|
|
layout : UShort
|
|
showsystem: bool
|
|
tempOffset : uint16_t
|
|
warning value: uint16_t
|
|
recent_client_addr : {0x00,0x00,0x00,0x00,0x00,0x00} array of bytes[6]
|
|
}*/
|
|
layout = 0;
|
|
pref.putUShort("layout", 0);
|
|
for (uint8_t i = 0; i < maxpidIndex; i++) { //read warning value from pid name if not found load from default
|
|
warningValue[i] = pref.getString(pidConfig[i][0].c_str(), pidConfig[i][8]);
|
|
}
|
|
pref.getBytes("recent_client", recent_client_addr, pref.getBytesLength("recent_client")); //read last bt address
|
|
|
|
|
|
//start bluetooth
|
|
if (!BTSerial.begin("Va&Cob OBDII Gauge", true)) {
|
|
Serial.println(F("Bluetooth..error!"));
|
|
Terminal("Bluetooth..error!", 0, 48, 320, 191);
|
|
abort();
|
|
} else {
|
|
Serial.println(F("Bluetooth..OK"));
|
|
Terminal("Bluetooth..OK", 0, 48, 320, 191);
|
|
}
|
|
|
|
#ifdef SKIP_CONNECTION
|
|
foundOBD2 = true;
|
|
#endif
|
|
|
|
//Connect to ELM327
|
|
connectLastOBDII(); //try connect last BT
|
|
while (!foundOBD2) { //not success try scan and connect another OBD2
|
|
scanBTdevice();
|
|
autoDim(); //auto backlight handle
|
|
}
|
|
|
|
//Initialize ELM327
|
|
Terminal("Initializing...", 0, 48, 320, 191);
|
|
bt_message = "";
|
|
for (uint8_t initIndex = 0; initIndex < elm327InitCount; initIndex++) { //send init ELM327 AT command
|
|
BTSerial.print(elm327Init[initIndex] + "\r");
|
|
if (initIndex == elm327InitCount - 1) delay(2000); //wait for 0100 Searching..protocal
|
|
else delay(100); //must add delay , unless will skip BTSerial.avaialble>0
|
|
while (BTSerial.available() > 0) {
|
|
char incomingChar = BTSerial.read();
|
|
if (incomingChar == '>') { //check prompt
|
|
BTSerial.flush(); //clear buffer
|
|
#ifdef SERIAL_DEBUG
|
|
Serial.println(bt_message);
|
|
#endif
|
|
#ifdef TERMINAL
|
|
Terminal(bt_message, 0, 48, 320, 191);
|
|
#endif
|
|
if (initIndex == 1) BTSerial.print("\r"); //ATBRD23 response
|
|
bt_message = "";
|
|
} else {
|
|
bt_message.concat(incomingChar); //keep elm327 respond
|
|
}
|
|
} //while
|
|
} //for
|
|
|
|
initScreen();
|
|
beepbeep();
|
|
prompt = true;
|
|
//----------------------
|
|
} //setup
|
|
|
|
/*###################################*/
|
|
void loop() {
|
|
updateBacklightFade(); // non-blocking smooth backlight fade handler
|
|
handleArcAlarmFlash(); // non-blocking arc alarm flash handler (blinks when in TFT_RED)
|
|
|
|
// SCAN TOUCHSCREEN
|
|
// - Single click on a quadrant: toggles between clock (arc) and number
|
|
// - Double click on a quadrant: puts that instrument in full screen (or exits full screen)
|
|
uint16_t touchX = 0, touchY = 0;
|
|
bool isTouched = getTouch(&touchX, &touchY);
|
|
|
|
static bool isTouching = false;
|
|
static uint32_t touchStartTime = 0;
|
|
static uint16_t lastTouchX = 0, lastTouchY = 0;
|
|
static uint8_t clickCount = 0;
|
|
static uint32_t firstClickTime = 0;
|
|
static int8_t clickQuadrant = -1;
|
|
|
|
if (isTouched) {
|
|
if (!isTouching) {
|
|
isTouching = true;
|
|
touchStartTime = millis();
|
|
lastTouchX = touchX;
|
|
lastTouchY = touchY;
|
|
}
|
|
} else {
|
|
// Touch released
|
|
if (isTouching) {
|
|
isTouching = false;
|
|
if (millis() - touchStartTime >= 30) {
|
|
// Valid tap detected! Determine quadrant
|
|
int quad = (lastTouchX < 160) ? ((lastTouchY < 120) ? 0 : 2) : ((lastTouchY < 120) ? 1 : 3);
|
|
|
|
if (clickCount == 0) {
|
|
clickCount = 1;
|
|
firstClickTime = millis();
|
|
clickQuadrant = quad;
|
|
} else if (clickCount == 1) {
|
|
if ((millis() - firstClickTime < 400) && (quad == clickQuadrant || fullScreenCell != -1)) {
|
|
// DOUBLE CLICK DETECTED!
|
|
clickCount = 0;
|
|
ledcWriteTone(buzzerChannel, 5000);
|
|
delay(15);
|
|
ledcWriteTone(buzzerChannel, 0);
|
|
delay(40);
|
|
ledcWriteTone(buzzerChannel, 5000);
|
|
delay(15);
|
|
ledcWriteTone(buzzerChannel, 0);
|
|
|
|
if (fullScreenCell == -1) {
|
|
fullScreenCell = quad;
|
|
} else {
|
|
fullScreenCell = -1;
|
|
}
|
|
initScreen();
|
|
} else {
|
|
clickCount = 1;
|
|
firstClickTime = millis();
|
|
clickQuadrant = quad;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle single click timeout (after 400ms without a second tap)
|
|
if (clickCount == 1 && (millis() - firstClickTime >= 400)) {
|
|
clickCount = 0;
|
|
ledcWriteTone(buzzerChannel, 4500);
|
|
delay(15);
|
|
ledcWriteTone(buzzerChannel, 0);
|
|
|
|
if (fullScreenCell != -1) {
|
|
// In full screen: single click toggles between arc and number mode without header flicker
|
|
cellIsNumeric[fullScreenCell] = !cellIsNumeric[fullScreenCell];
|
|
fullScreenMeter(fullScreenCell, pidInCell[fullScreenCell], false);
|
|
} else if (clickQuadrant >= 0 && clickQuadrant < 4) {
|
|
// In 4 quadrants: single click toggles between arc and number for this quadrant without header flicker
|
|
cellIsNumeric[clickQuadrant] = !cellIsNumeric[clickQuadrant];
|
|
uint8_t pid = pidInCell[clickQuadrant];
|
|
if (cellIsNumeric[clickQuadrant]) {
|
|
numericMeter4(clickQuadrant, pid, false);
|
|
} else {
|
|
arcMeter4(clickQuadrant, pid, false);
|
|
}
|
|
}
|
|
}
|
|
//----------------------
|
|
//BLUETOOTH read
|
|
static uint32_t lastPidSendTime = 0;
|
|
// Watchdog: If waiting for prompt for more than 6000ms (standard OBD2 maximum ECU timeout), recover!
|
|
if (!prompt && (millis() - lastPidSendTime > 6000)) {
|
|
#ifdef SERIAL_DEBUG
|
|
Serial.printf("PID timeout for %s, recovering...\n", pidList[pidIndex].c_str());
|
|
#endif
|
|
BTSerial.flush();
|
|
bt_message = "";
|
|
prompt = true;
|
|
skip = true;
|
|
}
|
|
|
|
while (BTSerial.available() > 0) {
|
|
char incomingChar = BTSerial.read();
|
|
if (incomingChar == '>') { //check prompt
|
|
BTSerial.flush(); //clear tx
|
|
#ifdef SERIAL_DEBUG
|
|
Serial.println(bt_message);
|
|
#endif
|
|
#ifdef TERMINAL //for degbugging
|
|
getAB(bt_message, pidList[pidIndex]);
|
|
Terminal(bt_message + "->" + String(A) + "," + String(B), 0, 48, 320, 191);
|
|
#endif
|
|
prompt = true; //pid response ready to calculate & display
|
|
} else {
|
|
bt_message.concat(incomingChar); //keep elm327 respond
|
|
}
|
|
}
|
|
//----------------------
|
|
//SERIAL PORT read
|
|
if (Serial.available() > 0) {
|
|
char incomingChar = Serial.read();
|
|
//check CR/NL
|
|
if ((incomingChar != '\r') && (incomingChar != '\n')) {
|
|
se_message.concat(incomingChar);
|
|
//Serial.print(incomingChar,HEX);
|
|
} else {
|
|
|
|
if (se_message != "") {
|
|
#ifdef TERMINAL
|
|
Terminal(se_message, 0, 48, 320, 191); //display terminal
|
|
#endif
|
|
BTSerial.print(se_message + "\r");
|
|
se_message = "";
|
|
Serial.flush();
|
|
} //if se_message
|
|
|
|
} //else if incomingChar
|
|
} //if Serial.available
|
|
|
|
//Send another PID to elm327
|
|
if (prompt) { //> ELM327 ready! -> request next PID
|
|
prompt = false; //no prompt from ELM327
|
|
if (!skip) {
|
|
updateMeter(pidIndex, bt_message); //update meter screen
|
|
}
|
|
pidIndex++; //point to next pid
|
|
if (pidIndex >= maxpidIndex) {
|
|
pidIndex = 0; //back to pid 0
|
|
autoDim(); //auto backlight handle
|
|
}
|
|
if (pidCurrentSkip[pidIndex] <= 0) { //end of skip loop , go next index
|
|
pidCurrentSkip[pidIndex] = pidReadSkip[pidIndex]; //read max delay loop to current
|
|
skip = false;
|
|
BTSerial.print(pidList[pidIndex] + "\r"); //send PID request
|
|
lastPidSendTime = millis();
|
|
} else { //skip sending request
|
|
pidCurrentSkip[pidIndex] = pidCurrentSkip[pidIndex] - 1; //decrese loop count
|
|
prompt = true;
|
|
skip = true;
|
|
} //else
|
|
|
|
} //if prompt
|
|
|
|
/*------------------*/
|
|
} // loop
|