#include #include #include namespace { constexpr uint16_t BG = 0x10A2; constexpr uint16_t CARD = 0x2145; constexpr uint16_t BORDER = 0x39E7; constexpr uint16_t MUTED = 0xAD55; constexpr uint16_t ACCENT = 0x4D7F; constexpr int32_t SLIDER_CANVAS_Y = 630; constexpr int32_t SLIDER_CANVAS_H = 90; struct Rect { int32_t x, y, w, h; bool contains(int32_t px, int32_t py) const { return px >= x && px < x + w && py >= y && py < y + h; } }; struct Button { Rect area; const char* label; uint16_t color; }; constexpr Rect colorPanel{28, 108, 760, 326}; constexpr Rect infoPanel{812, 108, 440, 506}; constexpr Rect slider{74, 661, 1132, 22}; Button buttons[] = { {{28, 462, 174, 126}, "RED", TFT_RED}, {{220, 462, 174, 126}, "GREEN", TFT_GREEN}, {{412, 462, 174, 126}, "BLUE", TFT_BLUE}, {{604, 462, 184, 126}, "RESET", 0x7BEF}, }; uint16_t panelColor = ACCENT; uint32_t pressCount = 0; uint32_t lastInfoUpdate = 0; uint8_t brightness = 160; int32_t lastTouchX = -1; int32_t lastTouchY = -1; M5Canvas sliderCanvas(&M5.Display); void centered(const char* text, const Rect& area, uint16_t color) { M5.Display.setFont(&fonts::FreeSansBold18pt7b); M5.Display.setTextDatum(middle_center); M5.Display.setTextColor(color); M5.Display.setTextSize(1); M5.Display.drawString(text, area.x + area.w / 2, area.y + area.h / 2); } void drawHeader() { M5.Display.fillRect(0, 0, M5.Display.width(), 82, CARD); M5.Display.fillRect(0, 80, M5.Display.width(), 2, ACCENT); M5.Display.setFont(&fonts::FreeSansBold24pt7b); M5.Display.setTextDatum(middle_left); M5.Display.setTextColor(TFT_WHITE); M5.Display.setTextSize(1); M5.Display.drawString("M5Stack Tab5", 28, 40); M5.Display.setFont(&fonts::FreeSans12pt7b); M5.Display.setTextDatum(middle_right); M5.Display.setTextColor(MUTED); M5.Display.setTextSize(1); M5.Display.drawString("TOUCH DASHBOARD / VARIATION 1", M5.Display.width() - 28, 40); } void drawColorPanel() { M5.Display.fillRoundRect(colorPanel.x, colorPanel.y, colorPanel.w, colorPanel.h, 22, panelColor); M5.Display.drawRoundRect(colorPanel.x, colorPanel.y, colorPanel.w, colorPanel.h, 22, TFT_WHITE); M5.Display.setFont(&fonts::FreeSansBold24pt7b); M5.Display.setTextDatum(middle_center); M5.Display.setTextColor(TFT_WHITE); M5.Display.setTextSize(1); M5.Display.drawString("DISPLAY + TOUCH READY", colorPanel.x + colorPanel.w / 2, colorPanel.y + 150); char text[48]; snprintf(text, sizeof(text), "Button presses: %lu", static_cast(pressCount)); M5.Display.setFont(&fonts::FreeSans18pt7b); M5.Display.drawString(text, colorPanel.x + colorPanel.w / 2, colorPanel.y + 250); } void drawButton(const Button& button, bool pressed = false) { uint16_t fill = pressed ? TFT_WHITE : button.color; uint16_t text = pressed ? button.color : TFT_WHITE; M5.Display.fillRoundRect(button.area.x, button.area.y, button.area.w, button.area.h, 18, fill); M5.Display.drawRoundRect(button.area.x, button.area.y, button.area.w, button.area.h, 18, TFT_WHITE); centered(button.label, button.area, text); } void drawSlider() { const int32_t localY = slider.y - SLIDER_CANVAS_Y; sliderCanvas.fillSprite(BG); sliderCanvas.setFont(&fonts::FreeSans12pt7b); sliderCanvas.setTextDatum(middle_left); sliderCanvas.setTextColor(MUTED, BG); sliderCanvas.setTextSize(1); sliderCanvas.drawString("BRIGHTNESS", 28, localY + slider.h / 2); sliderCanvas.fillRoundRect(slider.x, localY, slider.w, slider.h, slider.h / 2, BORDER); int32_t filled = map(brightness, 20, 255, 0, slider.w); if (filled > 0) { sliderCanvas.fillRoundRect(slider.x, localY, filled, slider.h, slider.h / 2, ACCENT); } int32_t knobX = slider.x + constrain(filled, 0, slider.w); sliderCanvas.fillCircle(knobX, localY + slider.h / 2, 20, TFT_WHITE); sliderCanvas.drawCircle(knobX, localY + slider.h / 2, 20, BORDER); char value[8]; snprintf(value, sizeof(value), "%u", brightness); sliderCanvas.setFont(&fonts::FreeSansBold12pt7b); sliderCanvas.setTextDatum(middle_right); sliderCanvas.setTextColor(TFT_WHITE, BG); sliderCanvas.drawString(value, M5.Display.width() - 28, localY + slider.h / 2); sliderCanvas.pushSprite(0, SLIDER_CANVAS_Y); } void infoLine(int32_t y, const char* label, const char* value) { M5.Display.fillRect(infoPanel.x + 18, y, infoPanel.w - 36, 38, CARD); M5.Display.setFont(&fonts::FreeSans12pt7b); M5.Display.setTextSize(1); M5.Display.setTextDatum(middle_left); M5.Display.setTextColor(MUTED, CARD); M5.Display.drawString(label, infoPanel.x + 24, y + 19); M5.Display.setFont(&fonts::FreeSansBold12pt7b); M5.Display.setTextDatum(middle_right); M5.Display.setTextColor(TFT_WHITE, CARD); M5.Display.drawString(value, infoPanel.x + infoPanel.w - 24, y + 19); } void updateInfo() { char value[64]; uint32_t seconds = millis() / 1000; snprintf(value, sizeof(value), "%02lu:%02lu:%02lu", static_cast(seconds / 3600), static_cast((seconds / 60) % 60), static_cast(seconds % 60)); infoLine(infoPanel.y + 82, "Uptime", value); snprintf(value, sizeof(value), "%d x %d", M5.Display.width(), M5.Display.height()); infoLine(infoPanel.y + 124, "Display", value); snprintf(value, sizeof(value), "%ld, %ld", static_cast(lastTouchX), static_cast(lastTouchY)); infoLine(infoPanel.y + 166, "Last touch", value); snprintf(value, sizeof(value), "%.1f MB", ESP.getFreeHeap() / (1024.0 * 1024.0)); infoLine(infoPanel.y + 208, "Free heap", value); snprintf(value, sizeof(value), "%.1f MB", ESP.getFreePsram() / (1024.0 * 1024.0)); infoLine(infoPanel.y + 250, "Free PSRAM", value); int32_t mv = M5.Power.getBatteryVoltage(); int32_t ma = M5.Power.getBatteryCurrent(); if (mv > 0) { snprintf(value, sizeof(value), "%.2f V / %ld mA", mv / 1000.0, static_cast(ma)); } else { snprintf(value, sizeof(value), "not detected"); } infoLine(infoPanel.y + 292, "Battery", value); snprintf(value, sizeof(value), "%s", WiFi.status() == WL_CONNECTED ? "connected" : "not connected"); infoLine(infoPanel.y + 334, "Wi-Fi", value); float ax = 0, ay = 0, az = 0; M5.Imu.update(); M5.Imu.getAccelData(&ax, &ay, &az); snprintf(value, sizeof(value), "%+.2f %+.2f %+.2f", ax, ay, az); infoLine(infoPanel.y + 376, "Accel XYZ", value); } void drawInfoPanel() { M5.Display.fillRoundRect(infoPanel.x, infoPanel.y, infoPanel.w, infoPanel.h, 22, CARD); M5.Display.drawRoundRect(infoPanel.x, infoPanel.y, infoPanel.w, infoPanel.h, 22, BORDER); M5.Display.setFont(&fonts::FreeSansBold18pt7b); M5.Display.setTextDatum(top_left); M5.Display.setTextColor(TFT_WHITE); M5.Display.setTextSize(1); M5.Display.drawString("LIVE DEVICE INFO", infoPanel.x + 24, infoPanel.y + 22); M5.Display.drawFastHLine(infoPanel.x + 24, infoPanel.y + 64, infoPanel.w - 48, BORDER); updateInfo(); } void setBrightness(int32_t x) { x = constrain(x, slider.x, slider.x + slider.w); brightness = static_cast( map(x, slider.x, slider.x + slider.w, 20, 255)); M5.Display.setBrightness(brightness); drawSlider(); } void activateButton(size_t index) { drawButton(buttons[index], true); delay(55); if (index < 3) { panelColor = buttons[index].color; ++pressCount; } else { panelColor = ACCENT; pressCount = 0; } drawColorPanel(); drawButton(buttons[index]); Serial.printf("Button %s pressed; count=%lu\n", buttons[index].label, static_cast(pressCount)); } void drawScreen() { M5.Display.startWrite(); M5.Display.fillScreen(BG); drawHeader(); drawColorPanel(); for (const auto& button : buttons) drawButton(button); drawInfoPanel(); drawSlider(); M5.Display.endWrite(); } } // namespace void setup() { Serial.begin(115200); auto config = M5.config(); M5.begin(config); M5.Display.setRotation(1); M5.Display.setColorDepth(16); M5.Display.setBrightness(brightness); sliderCanvas.setColorDepth(16); sliderCanvas.setPsram(true); sliderCanvas.createSprite(M5.Display.width(), SLIDER_CANVAS_H); drawScreen(); Serial.println("M5Stack Tab5 Touch Dashboard ready"); } void loop() { M5.update(); auto touch = M5.Touch.getDetail(); if (touch.isPressed()) { lastTouchX = touch.x; lastTouchY = touch.y; Rect sliderHit{slider.x - 20, slider.y - 28, slider.w + 40, slider.h + 56}; if (sliderHit.contains(touch.x, touch.y)) { setBrightness(touch.x); } else if (colorPanel.contains(touch.x, touch.y)) { M5.Display.drawCircle(touch.x, touch.y, 16, TFT_WHITE); } } if (touch.wasReleased()) { for (size_t i = 0; i < sizeof(buttons) / sizeof(buttons[0]); ++i) { if (buttons[i].area.contains(touch.x, touch.y)) { activateButton(i); break; } } } if (millis() - lastInfoUpdate >= 500) { lastInfoUpdate = millis(); updateInfo(); } delay(8); }