1240 lines
43 KiB
C
1240 lines
43 KiB
C
// All Meters display function
|
|
#include "DSEG7_Modern_BoldItalic_80.h"
|
|
#include "DSEG7_Modern_BoldItalic_44.h"
|
|
#include "DSEG7_Modern_BoldItalic_34.h"
|
|
#include "DSEG7_Modern_BoldItalic_24.h"
|
|
|
|
const uint16_t screenWidth = tft.height(); // landscape
|
|
const uint16_t screenHeight = tft.width();
|
|
/*---- METER ------_*
|
|
Type 0:numericMeter
|
|
Type 1:arcMeters
|
|
Type 2:vBarMeter
|
|
coordinate for each cell (numieric meter & arc meter)
|
|
1 , 4 , 7
|
|
2 , 5 , 8
|
|
3 , 6 , 9 */
|
|
const uint16_t cell1_x[9] = {
|
|
0, 0, 0, 80, 80, 80, 160, 160, 160}; // cell type 1 x origin cooridate
|
|
const uint16_t cell1_y[9] = {
|
|
0, 80, 160, 0, 80, 160, 0, 80, 160}; // cell type 1 y origin coordiate
|
|
/*coordinate for each cell type 2 (Vertical bar meter)
|
|
1 , 2 , 3 , 4 */
|
|
const uint16_t cell2_x[4] = {0, 80, 160,
|
|
240}; // vertical bar cell x origin coordinate
|
|
const uint16_t cell4_x[4] = {
|
|
0, 160, 0, 160}; // 4 quadrant x: top-left, top-right, bot-left, bot-right
|
|
const uint16_t cell4_y[4] = {
|
|
0, 0, 120, 120}; // 4 quadrant y: top-left, top-right, bot-left, bot-right
|
|
bool blinkCell[9] = {false, false, false, false, false,
|
|
false, false, false, false}; // flag for blinking
|
|
|
|
String pidList[maxpidIndex] =
|
|
{}; //{"0104","0105","010C","010B","010C","0142","015C"}
|
|
uint8_t pidReadSkip[maxpidIndex] = {}; // skip reading 0 - 3; 3 = max delay read
|
|
uint8_t pidCurrentSkip[maxpidIndex] = {}; // current skip counter
|
|
uint8_t engine_off_count = 0; // counter to indicate engine is off then sleep
|
|
float old_data[maxpidIndex] = {-999, -999, -999, -999}; // keep old data
|
|
float last_valid_data[maxpidIndex] = {-999, -999, -999, -999}; // keep last valid data for instant redraw
|
|
|
|
// backlight variable average counter
|
|
uint8_t low_count = 0;
|
|
uint8_t high_count = 0;
|
|
|
|
const float factoryECUOff = 12.5; // default off voltage
|
|
float ecu_off_volt = factoryECUOff; // turn engine off if voltage lower
|
|
// than 12.4
|
|
|
|
// Draw parameter icon (32x32 px) with transparent background (TFT_BLACK)
|
|
void drawPidIcon(int x, int y, const String &pidStr) {
|
|
if (pidStr == "0105") {
|
|
tft.pushImage(x, y, 32, 32, icon_temp_32, TFT_BLACK);
|
|
} else if (pidStr == "010D") {
|
|
tft.pushImage(x, y, 32, 32, icon_speed_32, TFT_BLACK);
|
|
} else if (pidStr == "010C") {
|
|
tft.pushImage(x, y, 32, 32, icon_rpm_32, TFT_BLACK);
|
|
} else if (pidStr == "0142" || pidStr == "ATRV") {
|
|
tft.pushImage(x, y, 32, 32, icon_battery_32, TFT_BLACK);
|
|
}
|
|
}
|
|
|
|
// Draw header icon (42x42 px, ~30% larger) for full screen mode
|
|
void drawPidIconHeader(int x, int y, const String &pidStr) {
|
|
if (pidStr == "0105") {
|
|
tft.pushImage(x, y, 42, 42, icon_temp_42, TFT_BLACK);
|
|
} else if (pidStr == "010D") {
|
|
tft.pushImage(x, y, 42, 42, icon_speed_42, TFT_BLACK);
|
|
} else if (pidStr == "010C") {
|
|
tft.pushImage(x, y, 42, 42, icon_rpm_42, TFT_BLACK);
|
|
} else if (pidStr == "0142" || pidStr == "ATRV") {
|
|
tft.pushImage(x, y, 42, 42, icon_battery_42, TFT_BLACK);
|
|
}
|
|
}
|
|
|
|
// Full Screen Exponential Segmented Block Bar Graph (20 blocks: 3mm width x 4mm height, 0.5mm gap, Anti-Aliased)
|
|
const uint8_t NUM_BAR_BLOCKS = 20;
|
|
const int16_t blockMinX[20] PROGMEM = { 0, 5, 11, 17, 25, 34, 46, 59, 76, 95, 114, 134, 154, 174, 193, 213, 232, 251, 271, 290 };
|
|
const int16_t blockMaxX[20] PROGMEM = { 28, 34, 40, 47, 55, 65, 76, 89, 104, 121, 138, 157, 175, 194, 214, 233, 252, 271, 290, 310 };
|
|
const int16_t blockMinY[20] PROGMEM = { 202, 183, 164, 146, 127, 110, 94, 80, 70, 63, 59, 56, 54, 53, 52, 52, 52, 52, 52, 52 };
|
|
const int16_t blockMaxY[20] PROGMEM = { 226, 208, 190, 172, 155, 139, 124, 111, 100, 92, 87, 83, 80, 79, 78, 77, 77, 77, 77, 77 };
|
|
|
|
const float blockNx[20][4] PROGMEM = {
|
|
{ 0.96709f, -0.26991f, -0.96711f, 0.24260f },
|
|
{ 0.95775f, -0.30696f, -0.95776f, 0.26991f },
|
|
{ 0.94416f, -0.35424f, -0.94418f, 0.30696f },
|
|
{ 0.92359f, -0.41544f, -0.92365f, 0.35424f },
|
|
{ 0.89126f, -0.49508f, -0.89141f, 0.41544f },
|
|
{ 0.83905f, -0.59638f, -0.83936f, 0.49508f },
|
|
{ 0.75533f, -0.71496f, -0.75581f, 0.59638f },
|
|
{ 0.63154f, -0.83065f, -0.63179f, 0.71496f },
|
|
{ 0.47873f, -0.91631f, -0.47824f, 0.83065f },
|
|
{ 0.33039f, -0.96398f, -0.32962f, 0.91631f },
|
|
{ 0.21408f, -0.98563f, -0.21356f, 0.96398f },
|
|
{ 0.13436f, -0.99446f, -0.13412f, 0.98563f },
|
|
{ 0.08319f, -0.99789f, -0.08309f, 0.99446f },
|
|
{ 0.05123f, -0.99920f, -0.05120f, 0.99789f },
|
|
{ 0.03149f, -0.99970f, -0.03148f, 0.99920f },
|
|
{ 0.01934f, -0.99989f, -0.01934f, 0.99970f },
|
|
{ 0.01188f, -0.99996f, -0.01187f, 0.99989f },
|
|
{ 0.00729f, -0.99998f, -0.00729f, 0.99996f },
|
|
{ 0.00448f, -0.99999f, -0.00448f, 0.99998f },
|
|
{ 0.00275f, -1.00000f, -0.00275f, 0.99999f },
|
|
};
|
|
|
|
const float blockNy[20][4] PROGMEM = {
|
|
{ 0.25444f, 0.96289f, -0.25437f, -0.97013f },
|
|
{ 0.28762f, 0.95172f, -0.28757f, -0.96289f },
|
|
{ 0.32950f, 0.93516f, -0.32942f, -0.95172f },
|
|
{ 0.38338f, 0.90962f, -0.38323f, -0.93516f },
|
|
{ 0.45348f, 0.86885f, -0.45320f, -0.90962f },
|
|
{ 0.54405f, 0.80270f, -0.54358f, -0.86885f },
|
|
{ 0.65535f, 0.69916f, -0.65480f, -0.80270f },
|
|
{ 0.77534f, 0.55679f, -0.77514f, -0.69916f },
|
|
{ 0.87796f, 0.40047f, -0.87823f, -0.55679f },
|
|
{ 0.94384f, 0.26596f, -0.94412f, -0.40047f },
|
|
{ 0.97682f, 0.16890f, -0.97693f, -0.26596f },
|
|
{ 0.99093f, 0.10510f, -0.99097f, -0.16890f },
|
|
{ 0.99653f, 0.06486f, -0.99654f, -0.10510f },
|
|
{ 0.99869f, 0.03990f, -0.99869f, -0.06486f },
|
|
{ 0.99950f, 0.02452f, -0.99950f, -0.03990f },
|
|
{ 0.99981f, 0.01506f, -0.99981f, -0.02452f },
|
|
{ 0.99993f, 0.00924f, -0.99993f, -0.01506f },
|
|
{ 0.99997f, 0.00568f, -0.99997f, -0.00924f },
|
|
{ 0.99999f, 0.00348f, -0.99999f, -0.00568f },
|
|
{ 1.00000f, 0.00223f, -1.00000f, -0.00348f },
|
|
};
|
|
|
|
const float blockC[20][4] PROGMEM = {
|
|
{ -57.0917f, -194.0217f, 79.0747f, 212.4570f },
|
|
{ -63.7256f, -171.8745f, 85.7128f, 191.2717f },
|
|
{ -71.2233f, -148.4987f, 93.2024f, 169.1245f },
|
|
{ -79.6909f, -123.2275f, 101.6563f, 145.7487f },
|
|
{ -89.0635f, -95.0180f, 111.0070f, 120.4775f },
|
|
{ -98.7898f, -62.4921f, 120.7056f, 92.2680f },
|
|
{ -107.1996f, -24.6792f, 129.0998f, 59.7421f },
|
|
{ -111.1224f, 16.8839f, 133.0287f, 21.9292f },
|
|
{ -107.7811f, 57.2365f, 129.6718f, -19.6339f },
|
|
{ -98.3836f, 92.0853f, 120.2644f, -59.9865f },
|
|
{ -87.1827f, 121.1162f, 109.0982f, -94.8353f },
|
|
{ -77.3181f, 146.0701f, 99.2734f, -123.8662f },
|
|
{ -69.7527f, 168.5916f, 91.7322f, -148.8201f },
|
|
{ -64.3207f, 189.7308f, 86.3119f, -171.3416f },
|
|
{ -60.5502f, 210.0867f, 82.5466f, -192.4808f },
|
|
{ -57.9834f, 229.9930f, 79.9819f, -212.8367f },
|
|
{ -56.2577f, 249.6371f, 78.2571f, -232.7430f },
|
|
{ -55.1079f, 269.1261f, 77.1076f, -252.3871f },
|
|
{ -54.3470f, 288.5222f, 76.3469f, -271.8761f },
|
|
{ -53.8454f, 307.8565f, 75.8453f, -291.2722f },
|
|
};
|
|
|
|
// Draw an individual solid rectangular block with smooth subpixel anti-aliasing
|
|
inline void drawBarBlock(uint8_t i, uint16_t color) {
|
|
int16_t minX = pgm_read_word(&blockMinX[i]);
|
|
int16_t maxX = pgm_read_word(&blockMaxX[i]);
|
|
int16_t minY = pgm_read_word(&blockMinY[i]);
|
|
int16_t maxY = pgm_read_word(&blockMaxY[i]);
|
|
|
|
float nx[4], ny[4], c[4];
|
|
for (uint8_t k = 0; k < 4; k++) {
|
|
nx[k] = pgm_read_float(&blockNx[i][k]);
|
|
ny[k] = pgm_read_float(&blockNy[i][k]);
|
|
c[k] = pgm_read_float(&blockC[i][k]);
|
|
}
|
|
|
|
tft.startWrite();
|
|
for (int16_t y = minY; y <= maxY; y++) {
|
|
float py = (float)y + 0.5f;
|
|
for (int16_t x = minX; x <= maxX; x++) {
|
|
float px = (float)x + 0.5f;
|
|
float min_d = 999.0f;
|
|
for (uint8_t k = 0; k < 4; k++) {
|
|
float d = px * nx[k] + py * ny[k] + c[k];
|
|
if (d < min_d) min_d = d;
|
|
}
|
|
if (min_d <= -0.5f) continue;
|
|
if (min_d >= 0.5f) {
|
|
tft.drawPixel(x, y, color);
|
|
} else {
|
|
uint8_t alpha = (uint8_t)((min_d + 0.5f) * 255.0f);
|
|
tft.drawPixel(x, y, color, alpha, TFT_BLACK);
|
|
}
|
|
}
|
|
}
|
|
tft.endWrite();
|
|
}
|
|
|
|
// 4-Quadrant Exponential Segmented Block Bar Graph (20 blocks per 160x120 quadrant, Anti-Aliased)
|
|
const int16_t quadBlockMinX[20] PROGMEM = { 0, 2, 5, 8, 12, 17, 22, 29, 37, 46, 56, 66, 76, 86, 96, 106, 115, 125, 135, 145 };
|
|
const int16_t quadBlockMaxX[20] PROGMEM = { 13, 15, 19, 22, 26, 31, 37, 44, 51, 60, 69, 79, 88, 98, 108, 117, 127, 137, 147, 156 };
|
|
const int16_t quadBlockMinY[20] PROGMEM = { 99, 89, 80, 71, 62, 53, 45, 38, 33, 30, 28, 27, 27, 27, 27, 27, 27, 27, 27, 27 };
|
|
const int16_t quadBlockMaxY[20] PROGMEM = { 112, 103, 94, 85, 76, 68, 60, 53, 47, 43, 40, 39, 38, 38, 38, 38, 38, 38, 38, 38 };
|
|
|
|
const float quadBlockNx[20][4] PROGMEM = {
|
|
{ -0.96559f, -0.27252f, 0.96559f, 0.24838f },
|
|
{ -0.95700f, -0.30564f, 0.95699f, 0.27558f },
|
|
{ -0.94472f, -0.34769f, 0.94471f, 0.30948f },
|
|
{ -0.92644f, -0.40223f, 0.92642f, 0.35263f },
|
|
{ -0.89793f, -0.47424f, 0.89788f, 0.40871f },
|
|
{ -0.85142f, -0.56935f, 0.85129f, 0.48283f },
|
|
{ -0.77354f, -0.68905f, 0.77328f, 0.58054f },
|
|
{ -0.64798f, -0.81800f, 0.64770f, 0.70233f },
|
|
{ -0.47589f, -0.91972f, 0.47607f, 0.83033f },
|
|
{ -0.29895f, -0.97319f, 0.29947f, 0.92739f },
|
|
{ -0.16408f, -0.99284f, 0.16442f, 0.97636f },
|
|
{ -0.08089f, -0.99843f, 0.08102f, 0.99382f },
|
|
{ -0.03600f, -0.99972f, 0.03604f, 0.99867f },
|
|
{ -0.01422f, -0.99996f, 0.01423f, 0.99977f },
|
|
{ -0.00481f, -1.00000f, 0.00481f, 0.99997f },
|
|
{ -0.00131f, -1.00000f, 0.00131f, 1.00000f },
|
|
{ -0.00026f, -1.00000f, 0.00026f, 1.00000f },
|
|
{ -0.00003f, -1.00000f, 0.00003f, 1.00000f },
|
|
{ -0.00000f, -1.00000f, 0.00000f, 1.00000f },
|
|
{ -0.00000f, -1.00000f, 0.00000f, 1.00000f },
|
|
};
|
|
|
|
const float quadBlockNy[20][4] PROGMEM = {
|
|
{ -0.26007f, 0.96215f, 0.26008f, -0.96866f },
|
|
{ -0.29010f, 0.95215f, 0.29012f, -0.96128f },
|
|
{ -0.32788f, 0.93761f, 0.32791f, -0.95090f },
|
|
{ -0.37644f, 0.91554f, 0.37650f, -0.93576f },
|
|
{ -0.44013f, 0.88039f, 0.44024f, -0.91266f },
|
|
{ -0.52449f, 0.82210f, 0.52469f, -0.87571f },
|
|
{ -0.63375f, 0.72471f, 0.63407f, -0.81423f },
|
|
{ -0.76166f, 0.57522f, 0.76190f, -0.71185f },
|
|
{ -0.87951f, 0.39257f, 0.87941f, -0.55728f },
|
|
{ -0.95427f, 0.23000f, 0.95411f, -0.37410f },
|
|
{ -0.98645f, 0.11943f, 0.98639f, -0.21615f },
|
|
{ -0.99672f, 0.05600f, 0.99671f, -0.11103f },
|
|
{ -0.99935f, 0.02355f, 0.99935f, -0.05152f },
|
|
{ -0.99990f, 0.00865f, 0.99990f, -0.02140f },
|
|
{ -0.99999f, 0.00264f, 0.99999f, -0.00774f },
|
|
{ -1.00000f, 0.00062f, 1.00000f, -0.00231f },
|
|
{ -1.00000f, 0.00010f, 1.00000f, -0.00052f },
|
|
{ -1.00000f, 0.00001f, 1.00000f, -0.00008f },
|
|
{ -1.00000f, 0.00000f, 1.00000f, -0.00000f },
|
|
{ -1.00000f, -0.00000f, 1.00000f, -0.00000f },
|
|
};
|
|
|
|
const float quadBlockC[20][4] PROGMEM = {
|
|
{ 37.4358f, -95.7104f, -29.4374f, 105.3111f },
|
|
{ 40.4052f, -84.6918f, -32.4076f, 94.5998f },
|
|
{ 43.7423f, -73.1663f, -35.7462f, 83.5384f },
|
|
{ 47.4998f, -60.8527f, -39.5065f, 71.9476f },
|
|
{ 51.6851f, -47.2838f, -43.6968f, 59.5307f },
|
|
{ 56.1380f, -31.7302f, -48.1577f, 45.7969f },
|
|
{ 60.2200f, -13.3339f, -52.2488f, 29.9893f },
|
|
{ 62.3450f, 7.8942f, -54.3778f, 11.2714f },
|
|
{ 60.4416f, 29.4505f, -52.4809f, -10.1523f },
|
|
{ 54.6617f, 47.9417f, -46.7062f, -31.5238f },
|
|
{ 47.9727f, 62.6966f, -40.0016f, -49.6097f },
|
|
{ 42.6961f, 74.9496f, -34.7083f, -64.0428f },
|
|
{ 39.3129f, 85.8690f, -31.3169f, -76.1142f },
|
|
{ 37.4353f, 96.1338f, -29.4363f, -86.9425f },
|
|
{ 36.5277f, 106.0939f, -28.5279f, -97.1639f },
|
|
{ 36.1555f, 115.9233f, -28.1555f, -107.1046f },
|
|
{ 36.0331f, 125.7043f, -28.0332f, -116.9264f },
|
|
{ 36.0042f, 135.4714f, -28.0042f, -126.7049f },
|
|
{ 36.0002f, 145.2358f, -28.0002f, -136.4714f },
|
|
{ 36.0000f, 155.0000f, -28.0000f, -146.2358f },
|
|
};
|
|
|
|
// Draw an individual solid rectangular block for 4-quadrant view with smooth subpixel anti-aliasing
|
|
inline void drawQuadBarBlock(uint8_t cell, uint8_t i, uint16_t color) {
|
|
int16_t ox = cell4_x[cell];
|
|
int16_t oy = cell4_y[cell];
|
|
int16_t minX = pgm_read_word(&quadBlockMinX[i]);
|
|
int16_t maxX = pgm_read_word(&quadBlockMaxX[i]);
|
|
int16_t minY = pgm_read_word(&quadBlockMinY[i]);
|
|
int16_t maxY = pgm_read_word(&quadBlockMaxY[i]);
|
|
|
|
float nx[4], ny[4], c[4];
|
|
for (uint8_t k = 0; k < 4; k++) {
|
|
nx[k] = pgm_read_float(&quadBlockNx[i][k]);
|
|
ny[k] = pgm_read_float(&quadBlockNy[i][k]);
|
|
c[k] = pgm_read_float(&quadBlockC[i][k]);
|
|
}
|
|
|
|
tft.startWrite();
|
|
for (int16_t y = minY; y <= maxY; y++) {
|
|
float py = (float)y + 0.5f;
|
|
int16_t sy = oy + y;
|
|
for (int16_t x = minX; x <= maxX; x++) {
|
|
float px = (float)x + 0.5f;
|
|
float min_d = 999.0f;
|
|
for (uint8_t k = 0; k < 4; k++) {
|
|
float d = px * nx[k] + py * ny[k] + c[k];
|
|
if (d < min_d) min_d = d;
|
|
}
|
|
if (min_d <= -0.5f) continue;
|
|
int16_t sx = ox + x;
|
|
if (min_d >= 0.5f) {
|
|
tft.drawPixel(sx, sy, color);
|
|
} else {
|
|
uint8_t alpha = (uint8_t)((min_d + 0.5f) * 255.0f);
|
|
tft.drawPixel(sx, sy, color, alpha, TFT_BLACK);
|
|
}
|
|
}
|
|
}
|
|
tft.endWrite();
|
|
}
|
|
|
|
/* ####################################
|
|
draw digital numeric meter 160x80 px
|
|
####################################
|
|
cell
|
|
1 , 2 , 3
|
|
4 , 5 , 6
|
|
7 , 8 , 9 */
|
|
void numericMeter(uint8_t cell, uint8_t pid) {
|
|
int w = screenWidth / 2;
|
|
int h = screenHeight / 3;
|
|
int x = cell1_x[cell - 1];
|
|
int y = cell1_y[cell - 1];
|
|
// Array data -> { Label , unit, pid, fomula, min, max, ,skip, digit }
|
|
String label = pidConfig[pid][0]; // get PID to label
|
|
tft.drawRect(x, y, w, screenHeight, TFT_GREY);
|
|
tft.fillRect(x + 2, y + 2, w - 5, h - 5, TFT_BLACK);
|
|
tft.fillRectVGradient(x + 2, y + 2, w - 5, h * 0.33, TFT_BLUE, 0x0011);
|
|
drawPidIcon(x + 4, y + 2, pidConfig[pid][2]);
|
|
tft.setTextColor(TFT_WHITE);
|
|
tft.drawCentreString(label, x + w / 2, y + 2, 4);
|
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
|
tft.drawRightString("---", x + w - 5, y + (h * 0.25) + 11, 6);
|
|
|
|
} // numericMeter
|
|
/*---------------------------*/
|
|
// plot number on digital Meter
|
|
void plotNumeric(uint8_t cell, float data, int warn, bool digit) {
|
|
int compare = warn / data; // compare data and warn
|
|
if (compare == 0) { // warning
|
|
int w = screenWidth / 2 - 1;
|
|
int h = screenHeight / 3 - 1;
|
|
int x = cell1_x[cell - 1];
|
|
int y = cell1_y[cell - 1];
|
|
if (blinkCell[cell]) { // show
|
|
String result = String(data, digit);
|
|
switch (result.length()) {
|
|
case 2:
|
|
result = " " + result;
|
|
break;
|
|
case 3:
|
|
result = " " + result;
|
|
break;
|
|
case 4:
|
|
result = " " + result;
|
|
break;
|
|
} // switch
|
|
old_data[pidIndex] = data; // update old data
|
|
tft.setTextColor(TFT_RED, TFT_BLACK); // red color font
|
|
tft.drawRightString(result.c_str(), x + w - 5, y + (h * 0.25) + 11, 6);
|
|
|
|
} else { // hiding make blinking effect
|
|
tft.fillRect(x + 2, y + 28, w - 5, 50, TFT_BLACK); // delete old number
|
|
}
|
|
blinkCell[cell] = !blinkCell[cell];
|
|
|
|
} else { // no warning
|
|
|
|
if (data != old_data[pidIndex]) { // if value change then redraw new data
|
|
int w = screenWidth / 2 - 1;
|
|
int h = screenHeight / 3 - 1;
|
|
int x = cell1_x[cell - 1];
|
|
int y = cell1_y[cell - 1];
|
|
|
|
String result = String(data, digit);
|
|
switch (result.length()) {
|
|
case 2:
|
|
result = " " + result;
|
|
break;
|
|
case 3:
|
|
result = " " + result;
|
|
break;
|
|
case 4:
|
|
result = " " + result;
|
|
break;
|
|
} // if data
|
|
tft.setTextColor(TFT_GREEN, TFT_BLACK); // green color font
|
|
tft.drawRightString(result.c_str(), x + w - 5, y + (h * 0.25) + 11, 6);
|
|
old_data[pidIndex] = data; // update old data
|
|
} // if nothing changed then skip
|
|
} // if warn/data == 0
|
|
}
|
|
|
|
/* ##########################
|
|
draw analog meter 160x80 px
|
|
##########################
|
|
cell
|
|
1 , 2 , 3
|
|
4 , 5 , 6
|
|
7 , 8 , 9 */
|
|
void arcMeter(uint8_t cell, uint8_t pid) {
|
|
int w = screenWidth / 2;
|
|
int h = screenHeight / 3;
|
|
int x = cell1_x[cell - 1];
|
|
int y = cell1_y[cell - 1];
|
|
// Array data -> { Label , unit, pid, fomula, min, max, ,skip, digit }
|
|
String label = pidConfig[pid][0]; // get PID to label
|
|
String unit = pidConfig[pid][1]; // get PID to unit
|
|
int min = pidConfig[pid][4].toInt(); // get PID to min
|
|
int max = pidConfig[pid][5].toInt(); // get PID to max
|
|
// Meter outline
|
|
tft.fillRect(x, y, w, h, TFT_GREY);
|
|
tft.fillRect(x + 2, y + 2, 156, 76, TFT_BLACK);
|
|
tft.setTextColor(TFT_WHITE); // Text colour
|
|
tft.drawCentreString(String(min).c_str(), x + 16, y + 10, 2);
|
|
tft.drawCentreString(String(max).c_str(), x + w - 16, y + 10, 2);
|
|
tft.drawRightString(unit.c_str(), x + 150, y + 60,
|
|
2); // Units at bottom right
|
|
tft.drawCentreString(label.c_str(), x + 80, y + 58,
|
|
2); // Comment out to avoid font 4
|
|
tft.setTextColor(TFT_GREEN);
|
|
tft.drawString("---", x + 10, y + 60, 2); // data
|
|
// draw arc animation
|
|
for (int angle = 135; angle < 225; angle++) {
|
|
tft.drawSmoothArc(x + 80, y + 90, 80, 60, 134, angle, TFT_GREEN,
|
|
TFT_DARKGREY, false);
|
|
}
|
|
for (int angle = 224; angle > 135; angle--) {
|
|
tft.drawSmoothArc(x + 80, y + 90, 80, 60, angle + 1, 226, TFT_WHITE,
|
|
TFT_DARKGREY, false);
|
|
}
|
|
}
|
|
// #########################################################################
|
|
void plotArc(uint8_t cell, String label, float data, int warn, int min, int max,
|
|
bool digit) {
|
|
if (data != old_data[pidIndex]) {
|
|
// int w = screenWidth / 2 - 1;
|
|
// int h = screenHeight / 3 - 1;
|
|
int x = cell1_x[cell - 1];
|
|
int y = cell1_y[cell - 1];
|
|
|
|
String result = String(data, digit);
|
|
switch (result.length()) {
|
|
case 2:
|
|
result = result + " ";
|
|
break;
|
|
case 3:
|
|
result = result + " ";
|
|
break;
|
|
case 4:
|
|
result = result + " ";
|
|
break;
|
|
}
|
|
|
|
if (data < min)
|
|
data = min;
|
|
if (data > max)
|
|
data = max;
|
|
|
|
int arcColor = TFT_GREEN;
|
|
int compare = warn / data; // compare data and warn
|
|
if (compare == 0) // warning
|
|
arcColor = TFT_RED; // red font red arc
|
|
else // no warn
|
|
arcColor = TFT_GREEN; // gren font green arc
|
|
tft.setTextColor(arcColor, TFT_BLACK);
|
|
tft.drawString(result.c_str(), x + 10, y + 60, 2); // data
|
|
int angle = map(data, min, max, 135, 225);
|
|
if (data >= old_data[pidIndex]) // new data > old data -> draw only green
|
|
// arc
|
|
tft.drawSmoothArc(x + 80, y + 90, 80, 60, 134, angle, arcColor,
|
|
TFT_DARKGREY, false);
|
|
else // new data < old data -> draw only white arc
|
|
tft.drawSmoothArc(x + 80, y + 90, 80, 60, angle + 1, 226, TFT_WHITE,
|
|
TFT_DARKGREY, false);
|
|
old_data[pidIndex] = data; // update old data
|
|
} // if
|
|
}
|
|
/* ######################################
|
|
draw linear vertical meter 80x240 px
|
|
######################################
|
|
cell 1 , 2 , 3 , 4 */
|
|
// vertical meter cell 1 to 4 full screen height
|
|
void vBarMeter(uint8_t cell, uint8_t pid) {
|
|
int x = cell2_x[cell - 1]; // start origin
|
|
int w = screenWidth / 4;
|
|
// Array data -> { Label , unit, pid, fomula, min, max, ,skip, digit }
|
|
String label = pidConfig[pid][0]; // get PID to label
|
|
String unit = pidConfig[pid][1]; // get PID to unit
|
|
// int min = pidConfig[pid][4].toInt(); //get PID to min
|
|
int max = pidConfig[pid][5].toInt(); // get PID to max
|
|
tft.drawRect(x, 0, w, screenHeight, TFT_DARKGREY);
|
|
tft.fillRectVGradient(x + 2, 2, w - 3, screenHeight * 0.12 - 10, TFT_BLUE,
|
|
0x0011);
|
|
tft.fillRect(x + 2, screenHeight * 0.12, w - 3,
|
|
screenHeight - screenHeight * 0.24, TFT_BLACK);
|
|
tft.setTextColor(TFT_WHITE);
|
|
tft.drawCentreString(label, x + w / 2, 2, 2);
|
|
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
|
tft.drawString(unit, x + 3, screenHeight * 0.12 - 8, 1);
|
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
|
tft.setTextPadding(w - 6);
|
|
tft.drawRightString("---", x + w - 4,
|
|
screenHeight - (screenHeight * 0.12) + 1, 4);
|
|
tft.setTextPadding(0);
|
|
float smalltick = screenHeight * 0.75 / 50;
|
|
// float bigtick = screenHeight*0.75/10;
|
|
// draw number
|
|
tft.setTextColor(TFT_WHITE, TFT_BLACK);
|
|
// tft.drawRigscreenHeighttString("120",x+w-2,y+screenHeight*0.12+2,1);
|
|
for (int i = 0; i < 51; i++) { // draw tick
|
|
tft.drawFastHLine(x + w / 3, screenHeight * 0.12 + smalltick * i + 3, 10,
|
|
TFT_WHITE);
|
|
if (i % 5 == 0) {
|
|
tft.drawFastHLine(x + w / 3, screenHeight * 0.12 + smalltick * i + 3, 20,
|
|
TFT_WHITE);
|
|
if (i % 10 == 0) {
|
|
int mark = max - (max * i * 2 / 100);
|
|
tft.drawRightString(String(mark), x + w - 3,
|
|
screenHeight * 0.12 + smalltick * i - 7, 2);
|
|
} // i%10
|
|
} // i%5
|
|
} // for i
|
|
// animation
|
|
for (int barH = 0; barH < 183; barH++) { // draw bar
|
|
tft.fillRect(x + 5, 212 - barH, 20, barH, TFT_RED); // lower
|
|
}
|
|
for (int barH = 182; barH >= 0; barH--) { // draw bar
|
|
tft.fillRect(x + 5, 29, 20, 183 - barH, TFT_BLACK); // upper
|
|
}
|
|
} // drawLinerVmeter
|
|
/*-------------------*/
|
|
// vertical meter cell 1 to 4 full screen height
|
|
void plotVBar(uint8_t cell, float data, int warn, int min, int max,
|
|
bool digit) { // cell 1-4
|
|
if (data != old_data[pidIndex]) { // if value change then redraw new data
|
|
int x = cell2_x[cell - 1];
|
|
int w = screenWidth / 4;
|
|
if (data < min)
|
|
data = min;
|
|
if (data > max)
|
|
data = max;
|
|
int barColor = TFT_GREEN;
|
|
int compare = warn / data; // compare data and warn
|
|
if (compare == 0) // warning
|
|
barColor = TFT_RED; // red bar red font
|
|
else // no warn
|
|
barColor = TFT_GREEN; // green bar green font
|
|
// draw new bar
|
|
int barH = map(data, min, max, 0, 182);
|
|
if (data > old_data[pidIndex]) // new data > old data -> draw red bar
|
|
tft.fillRectVGradient(x + 5, 212 - barH, 20, barH, barColor,
|
|
0x8000); // lower
|
|
else // new data < old data -> draw black bar
|
|
tft.fillRect(x + 5, 29, 20, 183 - barH, TFT_BLACK); // upper
|
|
|
|
tft.setTextColor(barColor, TFT_BLACK); // draw text
|
|
String result = String(data, digit);
|
|
switch (result.length()) {
|
|
case 2:
|
|
result = " " + result + " ";
|
|
break;
|
|
case 3:
|
|
result = " " + result + " ";
|
|
break;
|
|
case 4:
|
|
result = " " + result + " ";
|
|
break;
|
|
}
|
|
tft.setTextPadding(w - 6);
|
|
tft.drawRightString(result.c_str(), x + w - 4,
|
|
screenHeight - (screenHeight * 0.12) + 1, 4);
|
|
tft.setTextPadding(0);
|
|
old_data[pidIndex] = data; // update old data
|
|
} // if
|
|
}
|
|
/*-------------------*/
|
|
///*-------------------*/
|
|
// 7-segment bitmask table: a, b, c, d, e, f, g
|
|
static const uint8_t seg7_map[10] = {
|
|
0b00111111, // 0
|
|
0b00000110, // 1
|
|
0b01011011, // 2
|
|
0b01001111, // 3
|
|
0b01100110, // 4
|
|
0b01101101, // 5
|
|
0b01111101, // 6
|
|
0b00000111, // 7
|
|
0b01111111, // 8
|
|
0b01101111 // 9
|
|
};
|
|
|
|
void draw7SegString(int x, int y, const char *str, uint8_t dW, uint8_t dH,
|
|
uint8_t t, uint16_t fg, uint16_t bg) {
|
|
int curX = x;
|
|
while (*str) {
|
|
char c = *str++;
|
|
if (c >= '0' && c <= '9') {
|
|
uint8_t mask = seg7_map[c - '0'];
|
|
uint16_t ca = (mask & 0x01) ? fg : bg;
|
|
uint16_t cb = (mask & 0x02) ? fg : bg;
|
|
uint16_t cc = (mask & 0x04) ? fg : bg;
|
|
uint16_t cd = (mask & 0x08) ? fg : bg;
|
|
uint16_t ce = (mask & 0x10) ? fg : bg;
|
|
uint16_t cf = (mask & 0x20) ? fg : bg;
|
|
uint16_t cg = (mask & 0x40) ? fg : bg;
|
|
|
|
tft.fillRect(curX + t, y, dW - 2 * t, t, ca);
|
|
tft.fillRect(curX + dW - t, y + t, t, (dH / 2) - t, cb);
|
|
tft.fillRect(curX + dW - t, y + (dH / 2), t, (dH / 2) - t, cc);
|
|
tft.fillRect(curX + t, y + dH - t, dW - 2 * t, t, cd);
|
|
tft.fillRect(curX, y + (dH / 2), t, (dH / 2) - t, ce);
|
|
tft.fillRect(curX, y + t, t, (dH / 2) - t, cf);
|
|
tft.fillRect(curX + t, y + (dH / 2) - (t / 2), dW - 2 * t, t, cg);
|
|
|
|
curX += dW + t;
|
|
} else if (c == '-') {
|
|
tft.fillRect(curX + t, y + (dH / 2) - (t / 2), dW - 2 * t, t, fg);
|
|
curX += dW + t;
|
|
} else if (c == '.') {
|
|
tft.fillRect(curX + 1, y + dH - t, t, t, fg);
|
|
curX += t + 3;
|
|
} else if (c == ' ') {
|
|
curX += dW / 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
int get7SegWidth(const char *str, uint8_t dW, uint8_t t) {
|
|
int total = 0;
|
|
while (*str) {
|
|
char c = *str++;
|
|
if (c >= '0' && c <= '9')
|
|
total += dW + t;
|
|
else if (c == '-')
|
|
total += dW + t;
|
|
else if (c == '.')
|
|
total += t + 3;
|
|
else if (c == ' ')
|
|
total += dW / 2;
|
|
}
|
|
if (total > t)
|
|
total -= t;
|
|
return total;
|
|
}
|
|
|
|
// Draw blue header background for a 160x120 quadrant (25px height) with lateral and vertical smooth fade
|
|
void drawQuadHeaderGradient(uint8_t cell) {
|
|
int16_t ox = cell4_x[cell];
|
|
int16_t oy = cell4_y[cell];
|
|
uint16_t lineBuf[160];
|
|
const float fade_w_left = 12.0f;
|
|
const float fade_w_right = 12.0f;
|
|
const float x_right_start = 160.0f - fade_w_right;
|
|
|
|
tft.startWrite();
|
|
tft.setAddrWindow(ox, oy, 160, 25);
|
|
for (int16_t y = 0; y < 25; y++) {
|
|
float vy;
|
|
if (y < 14) {
|
|
vy = 1.0f;
|
|
} else {
|
|
float t = (24.0f - (float)y) / 10.0f;
|
|
vy = t * t * (3.0f - 2.0f * t);
|
|
}
|
|
for (int16_t x = 0; x < 160; x++) {
|
|
float hx = 1.0f;
|
|
if (x < (int16_t)fade_w_left) {
|
|
float t = (float)x / fade_w_left;
|
|
hx = t * t * (3.0f - 2.0f * t);
|
|
} else if (x > (int16_t)x_right_start) {
|
|
float t = (159.0f - (float)x) / fade_w_right;
|
|
if (t < 0.0f) t = 0.0f;
|
|
hx = t * t * (3.0f - 2.0f * t);
|
|
}
|
|
float b = vy * hx;
|
|
uint8_t g = (uint8_t)(140.0f * b);
|
|
uint8_t bl = (uint8_t)(255.0f * b);
|
|
lineBuf[x] = tft.color565(0, g, bl);
|
|
}
|
|
tft.pushPixels(lineBuf, 160);
|
|
}
|
|
tft.endWrite();
|
|
}
|
|
|
|
// Forward declarations
|
|
void plotNumeric4(uint8_t cell, float data, int warn, bool digit, String unit);
|
|
void plotArc4(uint8_t cell, float data, int warn, int min, int max, bool digit,
|
|
String unit);
|
|
void plotFullScreen(uint8_t cell, float data, int warn, int min, int max,
|
|
bool digit, String unit);
|
|
|
|
// 4-quadrant divider styling (2px visible automotive slate grey)
|
|
const uint16_t QUAD_DIV_COLOR = 0x632C;
|
|
|
|
inline void drawQuadrantDividers() {
|
|
tft.fillRect(159, 0, 2, 240, QUAD_DIV_COLOR);
|
|
tft.fillRect(0, 119, 320, 2, QUAD_DIV_COLOR);
|
|
}
|
|
|
|
// 4-quadrant Numeric Meter (160x120 px)
|
|
void numericMeter4(uint8_t cell, uint8_t pid, bool redrawHeader = true) {
|
|
int w = 160;
|
|
int h = 120;
|
|
int x = cell4_x[cell];
|
|
int y = cell4_y[cell];
|
|
int rx = (cell & 1) ? 161 : 0;
|
|
int ry = (cell & 2) ? 121 : 0;
|
|
|
|
tft.fillRect(rx, ry, 159, 119, TFT_BLACK);
|
|
drawPidIcon(x + 5, y + 5, pidConfig[pid][2]);
|
|
drawQuadrantDividers();
|
|
|
|
if (last_valid_data[cell] != -999) {
|
|
old_data[cell] = -999;
|
|
String unit = pidConfig[pid][1];
|
|
bool digit = pidConfig[pid][7].toInt();
|
|
int warn = warningValue[pid].toInt();
|
|
plotNumeric4(cell, last_valid_data[cell], warn, digit, unit);
|
|
} else {
|
|
// Initial placeholder: right-aligned DSEG7 44pt number
|
|
tft.loadFont(DSEG7_Modern_BoldItalic_44);
|
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
|
tft.setTextPadding(w - 12);
|
|
tft.drawRightString("---", x + w - 10, y + 46, 1);
|
|
tft.setTextPadding(0);
|
|
tft.unloadFont();
|
|
}
|
|
}
|
|
|
|
void plotNumeric4(uint8_t cell, float data, int warn, bool digit, String unit) {
|
|
if (data == old_data[cell])
|
|
return;
|
|
int x = cell4_x[cell];
|
|
int y = cell4_y[cell];
|
|
int w = 160;
|
|
bool isWarn = (warn > 0 && data >= warn);
|
|
uint16_t col = isWarn ? TFT_RED : TFT_GREEN;
|
|
|
|
String numStr = String(data, digit);
|
|
tft.loadFont(DSEG7_Modern_BoldItalic_44);
|
|
tft.setTextColor(col, TFT_BLACK);
|
|
tft.setTextPadding(w - 12);
|
|
tft.drawRightString(numStr.c_str(), x + w - 10, y + 46, 1);
|
|
tft.setTextPadding(0);
|
|
tft.unloadFont();
|
|
old_data[cell] = data;
|
|
}
|
|
|
|
// 4-quadrant Exponential Segmented Block Meter (160x120 px) - identical to full screen style
|
|
void arcMeter4(uint8_t cell, uint8_t pid, bool redrawHeader = true) {
|
|
int x = cell4_x[cell];
|
|
int y = cell4_y[cell];
|
|
int rx = (cell & 1) ? 161 : 0;
|
|
int ry = (cell & 2) ? 121 : 0;
|
|
|
|
tft.fillRect(rx, ry, 159, 119, TFT_BLACK);
|
|
drawPidIcon(x + 5, y + 3, pidConfig[pid][2]);
|
|
drawQuadrantDividers();
|
|
|
|
// Initialize all 20 blocks in DARKGREY
|
|
for (uint8_t i = 0; i < NUM_BAR_BLOCKS; i++) {
|
|
drawQuadBarBlock(cell, i, TFT_DARKGREY);
|
|
}
|
|
|
|
int min = pidConfig[pid][4].toInt();
|
|
int max = pidConfig[pid][5].toInt();
|
|
bool digit = pidConfig[pid][7].toInt();
|
|
int warn = warningValue[pid].toInt();
|
|
String unit = pidConfig[pid][1];
|
|
|
|
if (last_valid_data[cell] != -999) {
|
|
old_data[cell] = -999;
|
|
plotArc4(cell, last_valid_data[cell], warn, min, max, digit, unit);
|
|
} else {
|
|
// Value placeholder using DSEG7 Modern 34pt smooth font
|
|
tft.loadFont(DSEG7_Modern_BoldItalic_34);
|
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
|
tft.setTextPadding(114);
|
|
tft.drawRightString("---", x + 152, y + 74, 1);
|
|
tft.setTextPadding(0);
|
|
tft.unloadFont();
|
|
old_data[cell] = -999;
|
|
}
|
|
}
|
|
|
|
void plotArc4(uint8_t cell, float data, int warn, int min, int max, bool digit,
|
|
String unit) {
|
|
if (data == old_data[cell])
|
|
return;
|
|
int x = cell4_x[cell];
|
|
int y = cell4_y[cell];
|
|
if (data < min)
|
|
data = min;
|
|
if (data > max)
|
|
data = max;
|
|
bool isWarn = (warn > 0 && data >= warn);
|
|
uint16_t col = isWarn ? TFT_RED : TFT_GREEN;
|
|
|
|
String numStr = String(data, digit);
|
|
|
|
int newBlocks = map(data, min, max, 0, NUM_BAR_BLOCKS);
|
|
if (newBlocks < 0)
|
|
newBlocks = 0;
|
|
if (newBlocks > NUM_BAR_BLOCKS)
|
|
newBlocks = NUM_BAR_BLOCKS;
|
|
|
|
int oldBlocks = (old_data[cell] < min) ? 0 : map(old_data[cell], min, max, 0, NUM_BAR_BLOCKS);
|
|
if (oldBlocks < 0)
|
|
oldBlocks = 0;
|
|
if (oldBlocks > NUM_BAR_BLOCKS)
|
|
oldBlocks = NUM_BAR_BLOCKS;
|
|
|
|
bool wasWarn = (warn > 0 && old_data[cell] >= warn);
|
|
if (isWarn != wasWarn || old_data[cell] < min) {
|
|
for (uint8_t i = 0; i < newBlocks; i++) {
|
|
drawQuadBarBlock(cell, i, col);
|
|
}
|
|
for (uint8_t i = newBlocks; i < NUM_BAR_BLOCKS; i++) {
|
|
drawQuadBarBlock(cell, i, TFT_DARKGREY);
|
|
}
|
|
} else if (newBlocks > oldBlocks) {
|
|
for (uint8_t i = oldBlocks; i < newBlocks; i++) {
|
|
drawQuadBarBlock(cell, i, col);
|
|
}
|
|
} else if (newBlocks < oldBlocks) {
|
|
for (uint8_t i = newBlocks; i < oldBlocks; i++) {
|
|
drawQuadBarBlock(cell, i, TFT_DARKGREY);
|
|
}
|
|
}
|
|
|
|
tft.loadFont(DSEG7_Modern_BoldItalic_34);
|
|
tft.setTextColor(col, TFT_BLACK);
|
|
tft.setTextPadding(114);
|
|
tft.drawRightString(numStr.c_str(), x + 152, y + 74, 1);
|
|
tft.setTextPadding(0);
|
|
tft.unloadFont();
|
|
old_data[cell] = data;
|
|
}
|
|
|
|
// Draw blue header background with rich saturated body (top 30px full brightness)
|
|
// and compact lateral fades (16px), extended to 51px height without overlapping the original vumetro
|
|
void drawHeaderGradient() {
|
|
uint16_t lineBuf[320];
|
|
const float fade_w_left = 16.0f;
|
|
const float fade_w_right = 16.0f;
|
|
const float x_right_start = 320.0f - fade_w_right;
|
|
|
|
tft.startWrite();
|
|
tft.setAddrWindow(0, 0, 320, 51);
|
|
for (int16_t y = 0; y < 51; y++) {
|
|
float vy;
|
|
if (y < 30) {
|
|
vy = 1.0f;
|
|
} else {
|
|
float t = (50.0f - (float)y) / 20.0f;
|
|
vy = t * t * (3.0f - 2.0f * t);
|
|
}
|
|
for (int16_t x = 0; x < 320; x++) {
|
|
float hx = 1.0f;
|
|
if (x < (int16_t)fade_w_left) {
|
|
float t = (float)x / fade_w_left;
|
|
hx = t * t * (3.0f - 2.0f * t);
|
|
} else if (x > (int16_t)x_right_start) {
|
|
float t = (319.0f - (float)x) / fade_w_right;
|
|
if (t < 0.0f) t = 0.0f;
|
|
hx = t * t * (3.0f - 2.0f * t);
|
|
}
|
|
float b = vy * hx;
|
|
uint8_t g = (uint8_t)(140.0f * b);
|
|
uint8_t bl = (uint8_t)(255.0f * b);
|
|
lineBuf[x] = tft.color565(0, g, bl);
|
|
}
|
|
tft.pushPixels(lineBuf, 320);
|
|
}
|
|
tft.endWrite();
|
|
}
|
|
|
|
void fullScreenMeter(uint8_t cell, uint8_t pid, bool redrawHeader = true) {
|
|
fullScreenCell = cell;
|
|
String label = pidConfig[pid][0];
|
|
String unit = pidConfig[pid][1];
|
|
if (unit == "volt")
|
|
unit = "V";
|
|
|
|
if (redrawHeader) {
|
|
drawHeaderGradient();
|
|
drawPidIconHeader(10, 4, pidConfig[pid][2]);
|
|
tft.setTextColor(TFT_WHITE);
|
|
tft.drawCentreString(label.c_str(), 175, 12, 4);
|
|
}
|
|
// Clear only below header
|
|
tft.fillRect(0, 51, 320, 189, TFT_BLACK);
|
|
|
|
int min = pidConfig[pid][4].toInt();
|
|
int max = pidConfig[pid][5].toInt();
|
|
bool digit = pidConfig[pid][7].toInt();
|
|
int warn = warningValue[pid].toInt();
|
|
|
|
if (last_valid_data[cell] != -999) {
|
|
old_data[cell] = -999;
|
|
plotFullScreen(cell, last_valid_data[cell], warn, min, max, digit, unit);
|
|
} else {
|
|
if (!cellIsNumeric[cell]) {
|
|
// Exponential segmented block mode full screen (18 discrete blocks, 3mm x 5mm, 1mm gap)
|
|
for (uint8_t i = 0; i < NUM_BAR_BLOCKS; i++) {
|
|
drawBarBlock(i, TFT_DARKGREY);
|
|
}
|
|
|
|
tft.loadFont(DSEG7_Modern_BoldItalic_80);
|
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
|
tft.setTextPadding(240);
|
|
tft.drawRightString("---", 312, 148, 1);
|
|
tft.setTextPadding(0);
|
|
tft.unloadFont();
|
|
old_data[cell] = -999;
|
|
} else {
|
|
// Digital numeric mode full screen with DSEG7 Smooth Font (Anti-Aliased)
|
|
tft.loadFont(DSEG7_Modern_BoldItalic_80);
|
|
tft.setTextColor(TFT_GREEN, TFT_BLACK);
|
|
tft.setTextPadding(295);
|
|
tft.drawRightString("---", 295, 98, 1);
|
|
tft.setTextPadding(0);
|
|
tft.unloadFont();
|
|
}
|
|
}
|
|
}
|
|
|
|
void plotFullScreen(uint8_t cell, float data, int warn, int min, int max,
|
|
bool digit, String unit) {
|
|
if (data == old_data[cell])
|
|
return;
|
|
if (data < min)
|
|
data = min;
|
|
if (data > max)
|
|
data = max;
|
|
bool isWarn = (warn > 0 && data >= warn);
|
|
uint16_t col = isWarn ? TFT_RED : TFT_GREEN;
|
|
|
|
String numStr = String(data, digit);
|
|
|
|
if (!cellIsNumeric[cell]) {
|
|
int newBlocks = map(data, min, max, 0, NUM_BAR_BLOCKS);
|
|
if (newBlocks < 0)
|
|
newBlocks = 0;
|
|
if (newBlocks > NUM_BAR_BLOCKS)
|
|
newBlocks = NUM_BAR_BLOCKS;
|
|
|
|
int oldBlocks = (old_data[cell] < min) ? 0 : map(old_data[cell], min, max, 0, NUM_BAR_BLOCKS);
|
|
if (oldBlocks < 0)
|
|
oldBlocks = 0;
|
|
if (oldBlocks > NUM_BAR_BLOCKS)
|
|
oldBlocks = NUM_BAR_BLOCKS;
|
|
|
|
bool wasWarn = (warn > 0 && old_data[cell] >= warn);
|
|
if (isWarn != wasWarn || old_data[cell] < min) {
|
|
for (uint8_t i = 0; i < newBlocks; i++) {
|
|
drawBarBlock(i, col);
|
|
}
|
|
for (uint8_t i = newBlocks; i < NUM_BAR_BLOCKS; i++) {
|
|
drawBarBlock(i, TFT_DARKGREY);
|
|
}
|
|
} else if (newBlocks > oldBlocks) {
|
|
for (uint8_t i = oldBlocks; i < newBlocks; i++) {
|
|
drawBarBlock(i, col);
|
|
}
|
|
} else if (newBlocks < oldBlocks) {
|
|
for (uint8_t i = newBlocks; i < oldBlocks; i++) {
|
|
drawBarBlock(i, TFT_DARKGREY);
|
|
}
|
|
}
|
|
|
|
// Value bottom-right using DSEG7 Modern smooth font
|
|
tft.loadFont(DSEG7_Modern_BoldItalic_80);
|
|
tft.setTextColor(col, TFT_BLACK);
|
|
tft.setTextPadding(240);
|
|
tft.drawRightString(numStr.c_str(), 312, 148, 1);
|
|
tft.setTextPadding(0);
|
|
tft.unloadFont();
|
|
} else {
|
|
// Digital numeric mode full screen with DSEG7 Smooth Font (Anti-Aliased)
|
|
tft.loadFont(DSEG7_Modern_BoldItalic_80);
|
|
tft.setTextColor(col, TFT_BLACK);
|
|
tft.setTextPadding(295);
|
|
tft.drawRightString(numStr.c_str(), 295, 98, 1);
|
|
tft.setTextPadding(0);
|
|
tft.unloadFont();
|
|
}
|
|
old_data[cell] = data;
|
|
}
|
|
|
|
/*-------------------*/
|
|
// initialize display
|
|
void initScreen() {
|
|
#ifdef SERIAL_DEBUG
|
|
Serial.printf("Display init, fullScreenCell = %d\n", fullScreenCell);
|
|
#endif
|
|
for (int i = 0; i < maxpidIndex; i++) {
|
|
pidList[i] = pidConfig[pidInCell[i]][2]; // get PID to list
|
|
pidReadSkip[i] = pidConfig[pidInCell[i]][6].toInt(); // get read skip
|
|
pidCurrentSkip[i] = pidReadSkip[i]; // skip for skip reading pid
|
|
old_data[i] = -999; // clear old data
|
|
}
|
|
if (fullScreenCell >= 0 && fullScreenCell < 4) {
|
|
fullScreenMeter(fullScreenCell, pidInCell[fullScreenCell]);
|
|
} else {
|
|
tft.fillScreen(TFT_BLACK); // clear screen
|
|
for (uint8_t c = 0; c < 4; c++) {
|
|
uint8_t pid = pidInCell[c];
|
|
if (cellIsNumeric[c]) {
|
|
numericMeter4(c, pid);
|
|
} else {
|
|
arcMeter4(c, pid);
|
|
}
|
|
}
|
|
drawQuadrantDividers();
|
|
}
|
|
}
|
|
/*-------------------------------*/
|
|
|
|
// get A B from response return in global A B variable
|
|
void getAB(String elm_rsp, String currentPid) { // 41 05 2A 3C 01>
|
|
A = 0;
|
|
B = 0;
|
|
if (currentPid == "ATRV") {
|
|
return;
|
|
}
|
|
if (elm_rsp.indexOf("NO DATA") != -1 ||
|
|
elm_rsp.indexOf("UNABLE") != -1 ||
|
|
elm_rsp.indexOf("ERROR") != -1 ||
|
|
elm_rsp.indexOf("STOPPED") != -1) {
|
|
return;
|
|
}
|
|
|
|
// Normalize: replace carriage returns, newlines, and prompt chars with spaces
|
|
elm_rsp.replace('\r', ' ');
|
|
elm_rsp.replace('\n', ' ');
|
|
elm_rsp.replace('>', ' ');
|
|
|
|
// Extract expected PID hex byte (e.g. "0105" -> "05", "010C" -> "0C", "010D" -> "0D")
|
|
String pidByte = (currentPid.length() >= 4) ? currentPid.substring(2) : currentPid;
|
|
pidByte.toUpperCase();
|
|
|
|
// 1. Tokenized search: look for "41" followed by PID byte
|
|
String tokens[16];
|
|
uint8_t tokenCount = 0;
|
|
int startIdx = 0;
|
|
while (startIdx < elm_rsp.length() && tokenCount < 16) {
|
|
while (startIdx < elm_rsp.length() && elm_rsp.charAt(startIdx) == ' ') {
|
|
startIdx++;
|
|
}
|
|
if (startIdx >= elm_rsp.length()) break;
|
|
int endIdx = elm_rsp.indexOf(' ', startIdx);
|
|
if (endIdx == -1) endIdx = elm_rsp.length();
|
|
tokens[tokenCount++] = elm_rsp.substring(startIdx, endIdx);
|
|
startIdx = endIdx + 1;
|
|
}
|
|
|
|
for (uint8_t i = 0; i < tokenCount; i++) {
|
|
tokens[i].toUpperCase();
|
|
}
|
|
|
|
for (uint8_t i = 0; i + 1 < tokenCount; i++) {
|
|
if (tokens[i] == "41" && tokens[i + 1] == pidByte) {
|
|
if (i + 2 < tokenCount) {
|
|
A = strtol(tokens[i + 2].c_str(), NULL, 16);
|
|
}
|
|
if (i + 3 < tokenCount) {
|
|
B = strtol(tokens[i + 3].c_str(), NULL, 16);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 2. Compact string fallback (in case adapter sends responses without spaces, e.g. "410D32")
|
|
String compact = "";
|
|
for (int i = 0; i < elm_rsp.length(); i++) {
|
|
char c = elm_rsp.charAt(i);
|
|
if (c != ' ') compact += (char)toupper(c);
|
|
}
|
|
String targetPrefix = "41" + pidByte;
|
|
int fIdx = compact.indexOf(targetPrefix);
|
|
if (fIdx != -1) {
|
|
int dataIdx = fIdx + targetPrefix.length();
|
|
if (dataIdx + 2 <= compact.length()) {
|
|
A = strtol(compact.substring(dataIdx, dataIdx + 2).c_str(), NULL, 16);
|
|
}
|
|
if (dataIdx + 4 <= compact.length()) {
|
|
B = strtol(compact.substring(dataIdx + 2, dataIdx + 4).c_str(), NULL, 16);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 3. Positional fallback as in original author code
|
|
uint8_t charcnt = currentPid.length() / 2;
|
|
if (tokenCount > charcnt + 1) {
|
|
A = strtol(tokens[charcnt].c_str(), NULL, 16);
|
|
B = strtol(tokens[charcnt + 1].c_str(), NULL, 16);
|
|
} else if (tokenCount > charcnt) {
|
|
A = strtol(tokens[charcnt].c_str(), NULL, 16);
|
|
B = 0;
|
|
}
|
|
} // getAB
|
|
/*-------------------------------*/
|
|
void updateMeter(uint8_t pidNo, String response) { // update parameter on screen
|
|
if (pidNo >= maxpidIndex || pidNo >= 4)
|
|
return;
|
|
uint8_t pidIdx = pidInCell[pidNo];
|
|
if (pidIdx >= 4)
|
|
return;
|
|
String label = pidConfig[pidIdx][0];
|
|
String unit = pidConfig[pidIdx][1];
|
|
uint8_t formula = pidConfig[pidIdx][3].toInt();
|
|
int min = pidConfig[pidIdx][4].toInt();
|
|
int max = pidConfig[pidIdx][5].toInt();
|
|
bool digit = pidConfig[pidIdx][7].toInt();
|
|
int warn = warningValue[pidIdx].toInt();
|
|
|
|
getAB(response, pidList[pidNo]);
|
|
float data = 0.0;
|
|
switch (formula) {
|
|
case 0:
|
|
data = A * 0.145;
|
|
break; // psi
|
|
case 1:
|
|
data = A - 40;
|
|
break; // temp °C (ECT)
|
|
case 2:
|
|
data = A * 100.0 / 255;
|
|
break; // %
|
|
case 3:
|
|
data = (256 * A + B) / 4.0;
|
|
break; // rpm (ENG SPD)
|
|
case 4:
|
|
data = (256 * A + B) / 1000.0;
|
|
break; // volt (PCM Volt)
|
|
case 5:
|
|
data = (256 * A + B) / 16.0;
|
|
if (data > 150 || data < 0)
|
|
data = old_data[pidNo];
|
|
break;
|
|
case 6:
|
|
data = (A * 256 + B) * 5 / 72 - 18;
|
|
if (data > 150 || data < 0)
|
|
data = old_data[pidNo];
|
|
break;
|
|
case 7:
|
|
data = A; // km/h (PID 010D Vehicle Speed)
|
|
break;
|
|
case 8: {
|
|
// Battery Voltage from ELM327 command "ATRV" (e.g. "14.2V\r\r>")
|
|
String s = response;
|
|
s.trim();
|
|
int vIdx = s.indexOf('V');
|
|
if (vIdx == -1) vIdx = s.indexOf('v');
|
|
if (vIdx != -1) s = s.substring(0, vIdx);
|
|
int lastSpace = s.lastIndexOf(' ');
|
|
if (lastSpace != -1) s = s.substring(lastSpace + 1);
|
|
int lastCr = s.lastIndexOf('\r');
|
|
if (lastCr != -1) s = s.substring(lastCr + 1);
|
|
int lastLf = s.lastIndexOf('\n');
|
|
if (lastLf != -1) s = s.substring(lastLf + 1);
|
|
s.trim();
|
|
data = s.toFloat();
|
|
break;
|
|
}
|
|
}
|
|
|
|
last_valid_data[pidNo] = data;
|
|
|
|
// Alarm warning audio alert
|
|
if (warn > 0 && data >= warn) {
|
|
ledcWriteTone(buzzerChannel, 2000);
|
|
delay(10);
|
|
ledcWriteTone(buzzerChannel, 0);
|
|
}
|
|
|
|
if (fullScreenCell >= 0 && fullScreenCell < 4) {
|
|
if (pidNo == fullScreenCell) {
|
|
plotFullScreen(fullScreenCell, data, warn, min, max, digit, unit);
|
|
}
|
|
} else {
|
|
if (cellIsNumeric[pidNo]) {
|
|
plotNumeric4(pidNo, data, warn, digit, unit);
|
|
} else {
|
|
plotArc4(pidNo, data, warn, min, max, digit, unit);
|
|
}
|
|
}
|
|
bt_message = ""; // reset
|
|
}
|
|
/*-------------------------------*/
|
|
|
|
// Target and current brightness variables for smooth fade
|
|
uint8_t targetBacklight = 255;
|
|
float currentBacklight = 255.0f;
|
|
uint32_t lastBacklightStepTime = 0;
|
|
|
|
void updateBacklightFade() {
|
|
uint32_t now = millis();
|
|
if (now - lastBacklightStepTime >= 15) { // every 15ms step
|
|
lastBacklightStepTime = now;
|
|
if (abs(currentBacklight - (float)targetBacklight) > 0.5f) {
|
|
if (currentBacklight < targetBacklight) {
|
|
currentBacklight += 3.0f;
|
|
if (currentBacklight > targetBacklight)
|
|
currentBacklight = targetBacklight;
|
|
} else {
|
|
currentBacklight -= 3.0f;
|
|
if (currentBacklight < targetBacklight)
|
|
currentBacklight = targetBacklight;
|
|
}
|
|
ledcWrite(backlightChannel, (uint32_t)currentBacklight);
|
|
}
|
|
}
|
|
}
|
|
|
|
// auto dim backlight function
|
|
void autoDim() {
|
|
int light = analogRead(LDR_PIN); // read light
|
|
if (light > LIGHT_LEVEL) { // low light (dark)
|
|
low_count++;
|
|
if (low_count > 6) {
|
|
low_count = 6;
|
|
if (!dim) {
|
|
targetBacklight = 50; // smooth fade target: dim
|
|
dim = true;
|
|
#ifdef SERIAL_DEBUG
|
|
Serial.println(F("Backlight -> target dim (50)"));
|
|
#endif
|
|
}
|
|
}
|
|
high_count = 0;
|
|
|
|
} else { // bright light (day)
|
|
high_count++;
|
|
if (high_count > 6) {
|
|
high_count = 6;
|
|
if (dim) {
|
|
targetBacklight = 255; // smooth fade target: bright
|
|
dim = false;
|
|
#ifdef SERIAL_DEBUG
|
|
Serial.println(F("Backlight -> target bright (255)"));
|
|
#endif
|
|
}
|
|
}
|
|
low_count = 0;
|
|
}
|
|
updateBacklightFade();
|
|
} // autodim
|
|
//---------------------------
|
|
/*############################################*/
|