obd2_gauge/sketch/obd2_gauge/obd2_gauge.ino

511 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
if (digitalRead(SELECTOR_PIN) == LOW) { //button pressed
delay(50);
if (digitalRead(SELECTOR_PIN) == LOW) {
configMenu();
}
}
}
//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 & BUTTON
// - 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)
// - Long press (> 1s) on screen OR holding selector button: opens configuration menu
uint16_t touchX = 0, touchY = 0;
bool isTouched = getTouch(&touchX, &touchY);
bool btnPressed = (digitalRead(SELECTOR_PIN) == LOW);
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;
static bool longPressHandled = false;
if (isTouched || btnPressed) {
if (!isTouching) {
isTouching = true;
touchStartTime = millis();
lastTouchX = touchX;
lastTouchY = touchY;
longPressHandled = false;
casioBeep(2048, 24); // Instant Casio beep on every screen touch!
} else if (!longPressHandled && (millis() - touchStartTime > 1000)) {
// Long press (> 1s) or button hold: Open Config Menu!
longPressHandled = true;
clickCount = 0;
casioBeep(4096, 35); // High confirmation beep
configMenu();
if (foundOBD2) initScreen();
isTouching = false;
}
} else {
// Touch / button released
if (isTouching) {
isTouching = false;
if (!longPressHandled && (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;
casioBeep(4096, 20); // High confirmation beep on double tap
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;
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