#ifndef DISPLAY_H #define DISPLAY_H #include "Color.h" #include "Adafruit_NeoPixel.h" #include "Vector.h" #define SYMBOL_COUNT 26 #define SYMBOL_J 11 #define SYMBOL_X 12 #define SYMBOL_DASH 13 #define SYMBOL_PERCENT 14 #define SYMBOL_T 15 #define SYMBOL_A 16 #define SYMBOL_G 17 #define SYMBOL_E 18 #define SYMBOL_H 19 #define SYMBOL_R 20 #define SYMBOL_I 21 #define SYMBOL_L 22 #define SYMBOL_SPACE 23 #define DISPLAY_CHAR_WIDTH 3 #define DISPLAY_CHAR_HEIGHT 5 extern bool SYMBOLS[SYMBOL_COUNT][DISPLAY_CHAR_WIDTH * DISPLAY_CHAR_HEIGHT]; class Display { public: const uint8_t width; const uint8_t height; const size_t pixelCount; const size_t pixelByteCount; bool fpsShow = false; private: Adafruit_NeoPixel leds; milliseconds_t fpsLastMillis = 0; int fps = 0; Color *buffer = nullptr; uint8_t brightness = 10; public: Display(uint8_t width, uint8_t height) : width(width), height(height), pixelCount(width * height), pixelByteCount(pixelCount * sizeof(Color)), leds(pixelCount, GPIO_NUM_13) { buffer = (Color *) malloc(pixelByteCount); if (buffer == nullptr) { Serial.print("+-----------------------------------------------+\n"); Serial.print("| OUT OF MEMORY: Cannot allocate double-buffer! |\n"); Serial.print("+-----------------------------------------------+\n"); } } ~Display() { if (buffer == nullptr) { return; } free(buffer); buffer = nullptr; } void setup() { leds.begin(); clear(); flush(); } void loop() { calculateFPS(); drawFpsBorder(); if (isDirty()) { flush(); } } void setBrightness(uint8_t value) { brightness = value; } uint8_t getBrightness() const { return brightness; } void clear() { if (buffer == nullptr) { return; } memset(buffer, 0, pixelByteCount); } enum ALIGN { LEFT, RIGHT }; uint8_t print2(int x, int y, double valueDbl, Color colorPositive, Color colorZero, Color colorNegative, ALIGN align = RIGHT) { const Color color = valueDbl == 0 ? colorZero : (valueDbl < 0 ? colorNegative : colorPositive); return print2(x, y, valueDbl, color, align); } uint8_t print2(int x, int y, double valueDbl, Color color, ALIGN align = RIGHT) { if (isnan(valueDbl)) { x -= 3 * (DISPLAY_CHAR_WIDTH + 1) - 1; x += print(x, y, SYMBOL_DASH, color, true) + 1; x += print(x, y, SYMBOL_DASH, color, true) + 1; x += print(x, y, SYMBOL_DASH, color, true) + 1; return x; } const int value = (int) round(abs(valueDbl)); const bool negative = valueDbl < 0; const int digitCount = (int) max(1.0, floor(log10(value)) + 1); // log10 is -inf for value==0, hence the max(1.0, ...) if (align == RIGHT) { x -= ((negative ? 1 : 0) + digitCount) * (DISPLAY_CHAR_WIDTH + 1) - 1; } int divider = (int) pow(10, digitCount - 1); if (negative) { x += print(x, y, SYMBOL_DASH, color, true) + 1; } bool showIfZero = false; // Serial.printf("x=%d, y=%d, value=%d, align=%s, digitCount=%d, divider=%d\n", x, y, value, align == LEFT ? "LEFT" : "RIGHT", digitCount, divider); for (int digitPos = 0; digitPos < digitCount; ++digitPos) { const int digitVal = value / divider % 10; showIfZero |= digitVal != 0 || (digitPos == digitCount - 1); x += print(x, y, digitVal, color, showIfZero) + 1; // Serial.printf(" digitPos=%d, x=%d, y=%d, digitVal=%d, showIfZero=%s, divider=%d\n", digitPos, x, y, digitVal, showIfZero ? "true" : "false", divider); divider /= 10; } // Serial.println(); return x; } uint8_t print(uint8_t xPos, uint8_t yPos, uint8_t index, Color color, bool showIfZero) { if (index == 0 && !showIfZero) { return DISPLAY_CHAR_WIDTH; } if (index >= SYMBOL_COUNT) { Serial.printf("Cannot print2 symbol #%u.\n", index); index = SYMBOL_COUNT - 1; } bool *symbolBit = SYMBOLS[index]; for (uint8_t y = 0; y < DISPLAY_CHAR_HEIGHT; ++y) { for (uint8_t x = 0; x < DISPLAY_CHAR_WIDTH; ++x) { if (*(symbolBit++)) { set(xPos + x, yPos + y, color); } } } return DISPLAY_CHAR_WIDTH; } // TODO REMOVE QUICK & DIRTY uint8_t printM(uint8_t xPos, uint8_t yPos, Color color) { bool sym[5][5] = { {X,_,_,_,X}, {X,X,_,X,X}, {X,_,X,_,X}, {X,_,_,_,X}, {X,_,_,_,X}, }; for (int y = 0; y < countof(sym); ++y) { for (int x = 0; x < countof(sym[0]); ++x) { if (sym[y][x]) { set(xPos + x, yPos + y, color); } } } return countof(sym[0]); } static bool doCreeperBlink() { const auto now = millis(); static auto blink = false; static auto last = now; if (!blink) { if (now - last >= 3000) { last = now; if (random(3) == 0) { blink = true; } } } else { if (now - last >= 500) { last = now; blink = false; } } return blink; } // TODO REMOVE QUICK & DIRTY uint8_t printCreeper(uint8_t xPos, uint8_t yPos) { const auto creeperBlink = doCreeperBlink(); bool sym[8][8] = { {X, X, X, X, X, X, X, X}, {X, X, X, X, X, X, X, X}, {X, _, _, X, X, _, _, X}, {X, _, _, X, X, _, _, X}, {X, X, X, _, _, X, X, X}, {X, X, _, _, _, _, X, X}, {X, X, _, _, _, _, X, X}, {X, X, _, X, X, _, X, X}, }; for (int y = 0; y < countof(sym); ++y) { for (int x = 0; x < countof(sym[0]); ++x) { if (creeperBlink && ((x == 1 || x == 2) && y == 2)) { set(xPos + x, yPos + y, GREEN); } else { set(xPos + x, yPos + y, sym[y][x] ? GREEN : BLACK); } } } return countof(sym[0]); } // TODO REMOVE QUICK & DIRTY uint8_t printI(uint8_t xPos, uint8_t yPos, Color color) { for (int y = 0; y < 5; ++y) { set(xPos, yPos + y, color); } return 1; } void set(Vector pos, Color color) { set((uint8_t) round(pos.x), (uint8_t) round(pos.y), color); } void set(uint8_t x, uint8_t y, Color color) { if (x >= width || y >= height) { return; } if ((y % 2) != 0) { x = width - x - 1; } set(y * width + x, color); } void set(uint16_t index, Color color) { if (buffer == nullptr) { return; } buffer[index] = { // yes, correct order is GRB !!! (uint8_t) (color.g * brightness >> 8), (uint8_t) (color.r * brightness >> 8), (uint8_t) (color.b * brightness >> 8) }; } private: void flush() { if (buffer == nullptr) { return; } memcpy(leds.getPixels(), buffer, pixelByteCount); leds.show(); } bool isDirty() const { if (buffer == nullptr) { return false; } return memcmp(leds.getPixels(), buffer, pixelByteCount) != 0; } void calculateFPS() { fps = (int) round(1000.0 / (millis() - fpsLastMillis)); fpsLastMillis = millis(); } void drawFpsBorder() { if (!fpsShow) { return; } int frames = fps; Color color = RED; if (frames > 3 * 76) { frames -= 3 * 76; color = WHITE; } else if (frames > 2 * 76) { frames -= 2 * 76; color = BLUE; } else if (frames > 76) { frames -= 76; color = GREEN; } for (int x = 0; x <= width - 1 && frames-- > 0; x++) { set(x, 0, color); } for (int y = 0; y <= height - 1 && frames-- > 0; y++) { set(width - 1, y, color); } for (int x = width - 1; x >= 0 && frames-- > 0; x--) { set(x, height - 1, color); } for (int y = height - 1; y >= 0 && frames-- > 0; y--) { set(0, y, color); } } }; #endif