diff --git a/src/displays/metersdisplay.h b/src/displays/metersdisplay.h index 08ed1fd..bdcae2c 100644 --- a/src/displays/metersdisplay.h +++ b/src/displays/metersdisplay.h @@ -8,6 +8,8 @@ #include "actions/switchscreenaction.h" #include "globals.h" #include "utils.h" +#include "widgets/verticalmeter.h" +#include "widgets/vumeter.h" namespace { class MainMenu; @@ -25,59 +27,39 @@ public: void rotate(int offset) override; private: - // Draw the analogue meter on the screen - void analogMeter(); + VuMeter m_vuMeter; - // Update needle position - void plotNeedle(float value); - - // Draw a linear meter on the screen - void plotLinear(const char *label, int x, int y); - - // Adjust 6 linear meter pointer positions - void plotPointer(); - - static const constexpr auto TFT_GREY = 0x5AEB; - - float ltx = 0; // Saved x coord of bottom of needle - uint16_t osx = 120, osy = 120; // Saved x & y coords - - struct ValuePair { - int value = 0, - old_value = -1; - }; - std::array values; - - int d = 0; + static constexpr auto x = 40; + std::array meters{{ + VerticalMeter{"U f", 0*x, 160}, + VerticalMeter{"U b", 1*x, 160}, + VerticalMeter{"Ivl", 2*x, 160}, + VerticalMeter{"Ivr", 3*x, 160}, + VerticalMeter{"Ihl", 4*x, 160}, + VerticalMeter{"Ihr", 5*x, 160} + }}; }; void MetersDisplay::initScreen() { tft.fillScreen(TFT_BLACK); - analogMeter(); // Draw analogue meter + m_vuMeter.start(); - // Draw 6 linear meters - byte d = 40; - plotLinear("A0", 0, 160); - plotLinear("A1", 1 * d, 160); - plotLinear("A2", 2 * d, 160); - plotLinear("A3", 3 * d, 160); - plotLinear("A4", 4 * d, 160); - plotLinear("A5", 5 * d, 160); + for (auto &meter : meters) + meter.start(); } void MetersDisplay::redraw() { - d += 4; if (d >= 360) d = 0; + m_vuMeter.redraw(avgSpeedKmh); - // Create a Sine wave for testing - for (auto iter = std::begin(values); iter != std::end(values); iter++) - iter->value = 50 + 50 * sin((d + (std::distance(std::begin(values), iter) * 60)) * 0.0174532925); - - plotPointer(); - - plotNeedle(avgSpeedKmh); + meters[0].redraw(fixBatVoltage(controllers.front.feedback.batVoltage), 35, 50); + meters[1].redraw(fixBatVoltage(controllers.back.feedback.batVoltage), 35, 50); + meters[2].redraw(fixCurrent(controllers.front.feedback.left.current), -10, 10); + meters[3].redraw(fixCurrent(controllers.front.feedback.right.current), -10, 10); + meters[4].redraw(fixCurrent(controllers.back.feedback.left.current), -10, 10); + meters[5].redraw(fixCurrent(controllers.back.feedback.right.current), -10, 10); } void MetersDisplay::rotate(int offset) @@ -91,193 +73,4 @@ void MetersDisplay::rotate(int offset) switchScreen(); #endif } - -void MetersDisplay::analogMeter() -{ - // Meter outline - tft.fillRect(0, 0, 239, 126, TFT_GREY); - tft.fillRect(5, 3, 230, 119, TFT_WHITE); - - tft.setTextColor(TFT_BLACK); // Text colour - - // Draw ticks every 5 degrees from -50 to +50 degrees (100 deg. FSD swing) - for (int i = -50; i < 51; i += 5) { - // Long scale tick length - int tl = 15; - - // Coodinates of tick to draw - float sx = cos((i - 90) * 0.0174532925); - float sy = sin((i - 90) * 0.0174532925); - uint16_t x0 = sx * (100 + tl) + 120; - uint16_t y0 = sy * (100 + tl) + 140; - uint16_t x1 = sx * 100 + 120; - uint16_t y1 = sy * 100 + 140; - - // Coordinates of next tick for zone fill - float sx2 = cos((i + 5 - 90) * 0.0174532925); - float sy2 = sin((i + 5 - 90) * 0.0174532925); - int x2 = sx2 * (100 + tl) + 120; - int y2 = sy2 * (100 + tl) + 140; - int x3 = sx2 * 100 + 120; - int y3 = sy2 * 100 + 140; - - // Yellow zone limits - //if (i >= -50 && i < 0) { - // tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_YELLOW); - // tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_YELLOW); - //} - - // Green zone limits - if (i >= 0 && i < 25) { - tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREEN); - tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREEN); - } - - // Orange zone limits - if (i >= 25 && i < 50) { - tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_ORANGE); - tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_ORANGE); - } - - // Short scale tick length - if (i % 25 != 0) tl = 8; - - // Recalculate coords incase tick lenght changed - x0 = sx * (100 + tl) + 120; - y0 = sy * (100 + tl) + 140; - x1 = sx * 100 + 120; - y1 = sy * 100 + 140; - - // Draw tick - tft.drawLine(x0, y0, x1, y1, TFT_BLACK); - - // Check if labels should be drawn, with position tweaks - if (i % 25 == 0) { - // Calculate label positions - x0 = sx * (100 + tl + 10) + 120; - y0 = sy * (100 + tl + 10) + 140; - switch (i / 25) { - case -2: tft.drawCentreString("0", x0, y0 - 12, 2); break; - case -1: tft.drawCentreString("7.5", x0, y0 - 9, 2); break; - case 0: tft.drawCentreString("15", x0, y0 - 6, 2); break; - case 1: tft.drawCentreString("22.5", x0, y0 - 9, 2); break; - case 2: tft.drawCentreString("30", x0, y0 - 12, 2); break; - } - } - - // Now draw the arc of the scale - sx = cos((i + 5 - 90) * 0.0174532925); - sy = sin((i + 5 - 90) * 0.0174532925); - x0 = sx * 100 + 120; - y0 = sy * 100 + 140; - // Draw scale arc, don't draw the last part - if (i < 50) tft.drawLine(x0, y0, x1, y1, TFT_BLACK); - } - - tft.drawString("KM/h", 5 + 230 - 40, 119 - 20, 2); // Units at bottom right - tft.drawCentreString("KM/h", 120, 70, 4); // Comment out to avoid font 4 - tft.drawRect(5, 3, 230, 119, TFT_BLACK); // Draw bezel line - - plotNeedle(0.f); // Put meter needle at 0 -} - -void MetersDisplay::plotNeedle(float value) -{ - tft.setTextColor(TFT_BLACK, TFT_WHITE); - char buf[8]; dtostrf(value, 4, 0, buf); - tft.drawRightString(buf, 50, 119 - 25, 4); - - if (value < -3) value = -3; // Limit value to emulate needle end stops - if (value > 33) value = 33; - - float sdeg = map(value, -3, 33, -150, -30); // Map value to angle - // Calcualte tip of needle coords - float sx = cos(sdeg * 0.0174532925); - float sy = sin(sdeg * 0.0174532925); - - // Calculate x delta of needle start (does not start at pivot point) - float tx = tan((sdeg + 90) * 0.0174532925); - - // Erase old needle image - tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, TFT_WHITE); - tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, TFT_WHITE); - tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, TFT_WHITE); - - // Re-plot text under needle - tft.setTextColor(TFT_BLACK); - tft.drawCentreString("KM/h", 120, 70, 4); // // Comment out to avoid font 4 - - // Store new needle end coords for next erase - ltx = tx; - osx = sx * 98 + 120; - osy = sy * 98 + 140; - - // Draw the needle in the new postion, magenta makes needle a bit bolder - // draws 3 lines to thicken needle - tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, TFT_RED); - tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, TFT_MAGENTA); - tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, TFT_RED); -} - -void MetersDisplay::plotLinear(const char *label, int x, int y) -{ - int w = 36; - tft.drawRect(x, y, w, 155, TFT_GREY); - tft.fillRect(x + 2, y + 19, w - 3, 155 - 38, TFT_WHITE); - tft.setTextColor(TFT_CYAN, TFT_BLACK); - tft.drawCentreString(label, x + w / 2, y + 2, 2); - - for (int i = 0; i < 110; i += 10) - { - tft.drawFastHLine(x + 20, y + 27 + i, 6, TFT_BLACK); - } - - for (int i = 0; i < 110; i += 50) - { - tft.drawFastHLine(x + 20, y + 27 + i, 9, TFT_BLACK); - } - - tft.fillTriangle(x + 3, y + 127, x + 3 + 16, y + 127, x + 3, y + 127 - 5, TFT_RED); - tft.fillTriangle(x + 3, y + 127, x + 3 + 16, y + 127, x + 3, y + 127 + 5, TFT_RED); - - tft.drawCentreString("---", x + w / 2, y + 155 - 18, 2); -} - -void MetersDisplay::plotPointer() -{ - int dy = 187; - byte pw = 16; - - tft.setTextColor(TFT_GREEN, TFT_BLACK); - - // Move the 6 pointers one pixel towards new value - for (auto iter = std::begin(values); iter != std::end(values); iter++) - { - const auto i = std::distance(std::begin(values), iter); - - char buf[8]; - dtostrf(iter->value, 4, 0, buf); - tft.drawRightString(buf, i * 40 + 36 - 5, 187 - 27 + 155 - 18, 2); - - int dx = 3 + 40 * i; - if (iter->value < 0) iter->value = 0; // Limit value to emulate needle end stops - if (iter->value > 100) iter->value = 100; - - while (!(iter->value == iter->old_value)) { - dy = 187 + 100 - iter->old_value; - if (iter->old_value > iter->value) - { - tft.drawLine(dx, dy - 5, dx + pw, dy, TFT_WHITE); - iter->old_value--; - tft.drawLine(dx, dy + 6, dx + pw, dy + 1, TFT_RED); - } - else - { - tft.drawLine(dx, dy + 5, dx + pw, dy, TFT_WHITE); - iter->old_value++; - tft.drawLine(dx, dy - 6, dx + pw, dy - 1, TFT_RED); - } - } - } -} } diff --git a/src/globals.h b/src/globals.h index 9eca80a..4a22a48 100644 --- a/src/globals.h +++ b/src/globals.h @@ -28,6 +28,8 @@ char deviceName[32]; Settings settings; SettingsSaver settingsSaver; +constexpr auto TFT_GREY = 0x5AEB; + class Controllers : public std::array { public: diff --git a/src/utils.h b/src/utils.h index 38f621f..49992cb 100644 --- a/src/utils.h +++ b/src/utils.h @@ -54,7 +54,7 @@ float convertFromInch(float val) float fixCurrent(int16_t value) { - return std::abs(value/50.); + return -value/50.; } float fixBatVoltage(int16_t value) diff --git a/src/widgets/verticalmeter.h b/src/widgets/verticalmeter.h new file mode 100644 index 0000000..f282aa1 --- /dev/null +++ b/src/widgets/verticalmeter.h @@ -0,0 +1,77 @@ +#pragma once + +#include "globals.h" +#include "utils.h" + +namespace { +class VerticalMeter +{ +public: + VerticalMeter(const char *text, int x, int y); + + void start(); + void redraw(float value, float min, float max); + +private: + const char * const m_text; + const int m_x; + const int m_y; + + float m_oldValue{}; +}; + +VerticalMeter::VerticalMeter(const char *text, int x, int y) : + m_text{text}, m_x{x}, m_y{y} +{ +} + +void VerticalMeter::start() +{ + int w = 36; + tft.drawRect(m_x, m_y, w, 155, TFT_GREY); + tft.fillRect(m_x + 2, m_y + 19, w - 3, 155 - 38, TFT_WHITE); + tft.setTextColor(TFT_CYAN, TFT_BLACK); + tft.drawCentreString(m_text, m_x + w / 2, m_y + 2, 2); + + for (int i = 0; i < 110; i += 10) + tft.drawFastHLine(m_x + 20, m_y + 27 + i, 6, TFT_BLACK); + + for (int i = 0; i < 110; i += 50) + tft.drawFastHLine(m_x + 20, m_y + 27 + i, 9, TFT_BLACK); + + tft.fillTriangle(m_x + 3, m_y + 127, m_x + 3 + 16, m_y + 127, m_x + 3, m_y + 127 - 5, TFT_RED); + tft.fillTriangle(m_x + 3, m_y + 127, m_x + 3 + 16, m_y + 127, m_x + 3, m_y + 127 + 5, TFT_RED); + + tft.drawCentreString("---", m_x + w / 2, m_y + 155 - 18, 2); +} + +void VerticalMeter::redraw(float value, float min, float max) +{ + tft.setTextColor(TFT_GREEN, TFT_BLACK); + + char buf[8]; + dtostrf(value, 4, 0, buf); + tft.drawRightString(buf, m_x + 36 - 5, 187 - 27 + 155 - 18, 2); + + const int dx = 3 + m_x; + value = scaleBetween(value, min, max, 0.f, 100.f); + + while (m_oldValue > value) + { + const int dy = 187 + 100 - m_oldValue; + tft.drawLine(dx, dy - 5, dx + 16, dy, TFT_WHITE); + m_oldValue--; + tft.drawLine(dx, dy + 6, dx + 16, dy + 1, TFT_RED); + } + + while (m_oldValue < value) + { + const int dy = 187 + 100 - m_oldValue; + tft.drawLine(dx, dy + 5, dx + 16, dy, TFT_WHITE); + m_oldValue++; + tft.drawLine(dx, dy - 6, dx + 16, dy - 1, TFT_RED); + } +} + + +} diff --git a/src/widgets/vumeter.h b/src/widgets/vumeter.h new file mode 100644 index 0000000..a28a4a4 --- /dev/null +++ b/src/widgets/vumeter.h @@ -0,0 +1,145 @@ +#pragma once + +#include "globals.h" + +namespace { +class VuMeter +{ +public: + void start(); + void redraw(float value); + +private: + float ltx; // Saved x coord of bottom of needle + uint16_t osx, osy; // Saved x & y coords +}; + +void VuMeter::start() +{ + ltx = 0; + osx = 120; + osy = 120; + + // Meter outline + tft.fillRect(0, 0, 239, 126, TFT_GREY); + tft.fillRect(5, 3, 230, 119, TFT_WHITE); + + tft.setTextColor(TFT_BLACK); // Text colour + + // Draw ticks every 5 degrees from -50 to +50 degrees (100 deg. FSD swing) + for (int i = -50; i < 51; i += 5) { + // Long scale tick length + int tl = 15; + + // Coodinates of tick to draw + float sx = cos((i - 90) * 0.0174532925); + float sy = sin((i - 90) * 0.0174532925); + uint16_t x0 = sx * (100 + tl) + 120; + uint16_t y0 = sy * (100 + tl) + 140; + uint16_t x1 = sx * 100 + 120; + uint16_t y1 = sy * 100 + 140; + + // Coordinates of next tick for zone fill + float sx2 = cos((i + 5 - 90) * 0.0174532925); + float sy2 = sin((i + 5 - 90) * 0.0174532925); + int x2 = sx2 * (100 + tl) + 120; + int y2 = sy2 * (100 + tl) + 140; + int x3 = sx2 * 100 + 120; + int y3 = sy2 * 100 + 140; + + // Yellow zone limits + //if (i >= -50 && i < 0) { + // tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_YELLOW); + // tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_YELLOW); + //} + + // Green zone limits + if (i >= 0 && i < 25) { + tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREEN); + tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREEN); + } + + // Orange zone limits + if (i >= 25 && i < 50) { + tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_ORANGE); + tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_ORANGE); + } + + // Short scale tick length + if (i % 25 != 0) tl = 8; + + // Recalculate coords incase tick lenght changed + x0 = sx * (100 + tl) + 120; + y0 = sy * (100 + tl) + 140; + x1 = sx * 100 + 120; + y1 = sy * 100 + 140; + + // Draw tick + tft.drawLine(x0, y0, x1, y1, TFT_BLACK); + + // Check if labels should be drawn, with position tweaks + if (i % 25 == 0) { + // Calculate label positions + x0 = sx * (100 + tl + 10) + 120; + y0 = sy * (100 + tl + 10) + 140; + switch (i / 25) { + case -2: tft.drawCentreString("0", x0, y0 - 12, 2); break; + case -1: tft.drawCentreString("7.5", x0, y0 - 9, 2); break; + case 0: tft.drawCentreString("15", x0, y0 - 6, 2); break; + case 1: tft.drawCentreString("22.5", x0, y0 - 9, 2); break; + case 2: tft.drawCentreString("30", x0, y0 - 12, 2); break; + } + } + + // Now draw the arc of the scale + sx = cos((i + 5 - 90) * 0.0174532925); + sy = sin((i + 5 - 90) * 0.0174532925); + x0 = sx * 100 + 120; + y0 = sy * 100 + 140; + // Draw scale arc, don't draw the last part + if (i < 50) tft.drawLine(x0, y0, x1, y1, TFT_BLACK); + } + + tft.drawString("KM/h", 5 + 230 - 40, 119 - 20, 2); // Units at bottom right + tft.drawCentreString("KM/h", 120, 70, 4); // Comment out to avoid font 4 + tft.drawRect(5, 3, 230, 119, TFT_BLACK); // Draw bezel line +} + +void VuMeter::redraw(float value) +{ + tft.setTextColor(TFT_BLACK, TFT_WHITE); + char buf[8]; dtostrf(value, 4, 0, buf); + tft.drawRightString(buf, 50, 119 - 25, 4); + + if (value < -3) value = -3; // Limit value to emulate needle end stops + if (value > 33) value = 33; + + float sdeg = map(value, -3, 33, -150, -30); // Map value to angle + // Calcualte tip of needle coords + float sx = cos(sdeg * 0.0174532925); + float sy = sin(sdeg * 0.0174532925); + + // Calculate x delta of needle start (does not start at pivot point) + float tx = tan((sdeg + 90) * 0.0174532925); + + // Erase old needle image + tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, TFT_WHITE); + tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, TFT_WHITE); + tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, TFT_WHITE); + + // Re-plot text under needle + tft.setTextColor(TFT_BLACK); + tft.drawCentreString("KM/h", 120, 70, 4); // // Comment out to avoid font 4 + + // Store new needle end coords for next erase + ltx = tx; + osx = sx * 98 + 120; + osy = sy * 98 + 140; + + // Draw the needle in the new postion, magenta makes needle a bit bolder + // draws 3 lines to thicken needle + tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, TFT_RED); + tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, TFT_MAGENTA); + tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, TFT_RED); +} +}