Files
Shadowtrance dbf850c434 Fixes and new apps (#30)
- M5 Unit Modules library + M5 Unit Test app
- Minor fixes for TodoList, TwoEleven, Snake, Brainfuck and Breakout
- Fixed SerialConsole to use the uart controller as it was broken in one of the many updates
- Fixed keyboard input in TwoEleven, Breakout, Magic8Ball and Snake
- Bluetooth Media Keys app, supports pressing physical keys to trigger the corresponding buttonmatrix button
- Epub Reader app
2026-06-07 15:56:32 +02:00

177 lines
7.8 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* UnitLcd.h - M5Stack LCD Unit (ESP32-PICO inside, I2C addr 0x3E)
*
* 135×240 IPS display driven by an internal ESP32 that bridges I2C commands
* to an ST7789V2. Unlike the STM32-based units there is no register map -
* instead you send drawing commands into a command buffer. The internal ESP32
* processes them independently, so writes are fire-and-forget for most ops.
*
* Key differences from STM32 units:
* - No STOP+delay+read pattern - standard repeated-START reads work fine
* - No 2ms inter-transaction delay needed
* - Pixel data goes in a single uninterrupted I2C transaction (WRITE_RAW)
* - Check READ_BUFCOUNT (0x09) before large writes to avoid overflow
* - Max I2C clock: 400 kHz
*
* Physical display: 135 wide × 240 tall (portrait, rotation 0/2)
* 240 wide × 135 tall (landscape, rotation 1/3)
*
* Coordinate system: origin top-left, X right, Y down, in the current
* rotation's logical space. Use width()/height() for safe bounds.
*
* Command quick-reference:
* 0x22 [brightness] Set backlight (0=off, 255=max)
* 0x36 [rotation] Set rotation (0-3 = 0/90/180/270°)
* 0x2A [x0] [x1] Set X window (column range, inclusive)
* 0x2B [y0] [y1] Set Y window (row range, inclusive)
* 0x68 [x0][y0][x1][y1] Fill rect with stored colour
* 0x6A [x0][y0][x1][y1][hi][lo] Fill rect with RGB565 colour inline
* 0x62 [x][y][hi][lo] Draw pixel RGB565
* 0x42 [pixels...] Write raw RGB565 stream (STOP ends it)
* 0x09 Read buffer remaining count (1 byte)
*/
#pragma once
#include <UnitCommon.h>
#include <cstdint>
class UnitLcd {
public:
static constexpr uint8_t DEFAULT_ADDR = 0x3E;
static constexpr uint16_t PHYS_WIDTH = 135;
static constexpr uint16_t PHYS_HEIGHT = 240;
// Legacy constants - equal to physical portrait dimensions.
static constexpr uint16_t WIDTH = PHYS_WIDTH;
static constexpr uint16_t HEIGHT = PHYS_HEIGHT;
UnitLcd() = default;
UnitLcd(const UnitLcd&) = delete;
UnitLcd& operator=(const UnitLcd&) = delete;
// Pass a I2C controller device
// Probe and initialise. Sets brightness to 128, rotation to 0.
[[nodiscard]] bool begin(Device* dev, uint8_t addr = DEFAULT_ADDR);
bool isPresent() const { return dev_ != nullptr; }
// Logical dimensions in the current rotation's coordinate space.
uint16_t width() const { return (rotation_ & 1) ? PHYS_HEIGHT : PHYS_WIDTH; }
uint16_t height() const { return (rotation_ & 1) ? PHYS_WIDTH : PHYS_HEIGHT; }
uint8_t rotation() const { return rotation_; }
// -----------------------------------------------------------------------
// Control
// -----------------------------------------------------------------------
void setBrightness(uint8_t brightness);
// Rotation: 0=portrait(135×240), 1=landscape(240×135),
// 2=portrait flipped, 3=landscape flipped.
void setRotation(uint8_t rotation);
// -----------------------------------------------------------------------
// Filled primitives (fast hardware commands)
// -----------------------------------------------------------------------
void fillScreen(uint16_t rgb565);
void fillRect(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint16_t rgb565);
void drawPixel(uint8_t x, uint8_t y, uint16_t rgb565);
// -----------------------------------------------------------------------
// Raw pixel streaming
// -----------------------------------------------------------------------
bool setWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1);
void writePixels(const uint16_t* pixels, uint32_t len);
// -----------------------------------------------------------------------
// Software-rendered shapes (Bresenham / midpoint algorithms)
// -----------------------------------------------------------------------
// Horizontal / vertical lines (faster than drawLine for axis-aligned).
void drawHLine(uint8_t x, uint8_t y, uint8_t len, uint16_t rgb565);
void drawVLine(uint8_t x, uint8_t y, uint8_t len, uint16_t rgb565);
// Arbitrary line (Bresenham).
void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t rgb565);
// Rectangle outline.
void drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t rgb565);
// Filled / outline circle (midpoint algorithm).
void fillCircle(int16_t cx, int16_t cy, int16_t r, uint16_t rgb565);
void drawCircle(int16_t cx, int16_t cy, int16_t r, uint16_t rgb565);
// Rounded rectangle (filled / outline).
void fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t rgb565);
void drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t rgb565);
// Triangle (filled / outline).
void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
int16_t x2, int16_t y2, uint16_t rgb565);
void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
int16_t x2, int16_t y2, uint16_t rgb565);
// Arc - angles in degrees (0=right, 90=down, 180=left, 270=up).
// r0 = outer radius, r1 = inner radius (0 for solid filled wedge).
// fillArc draws a filled arc/wedge; drawArc draws the outline only.
void fillArc(int16_t cx, int16_t cy, int16_t r0, int16_t r1,
float startDeg, float endDeg, uint16_t rgb565);
void drawArc(int16_t cx, int16_t cy, int16_t r0, int16_t r1,
float startDeg, float endDeg, uint16_t rgb565);
// -----------------------------------------------------------------------
// Text rendering - 5×7 bitmap font (ASCII 32-126)
// -----------------------------------------------------------------------
// Draw a single character at (x, y). scale 1=5×7 px, 2=10×14 px, …
void drawChar(uint8_t x, uint8_t y, char c, uint16_t fg, uint16_t bg, uint8_t scale = 1);
// Draw a null-terminated string starting at (x, y), advancing 6*scale px per char.
void drawText(uint8_t x, uint8_t y, const char* str, uint16_t fg, uint16_t bg, uint8_t scale = 1);
// -----------------------------------------------------------------------
// Misc
// -----------------------------------------------------------------------
uint8_t bufferRemaining();
static uint16_t rgb888to565(uint32_t rgb888) {
uint8_t r = (rgb888 >> 16) & 0xFF;
uint8_t g = (rgb888 >> 8) & 0xFF;
uint8_t b = rgb888 & 0xFF;
return (uint16_t)(((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3));
}
// color565 alias used by M5GFX-style code
static uint16_t color565(uint8_t r, uint8_t g, uint8_t b) {
return (uint16_t)(((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3));
}
private:
Device* dev_ = nullptr;
uint8_t addr_ = DEFAULT_ADDR;
uint8_t rotation_ = 0;
bool sendCmd(const uint8_t* data, uint16_t len);
// Helper: draw pixel only if in bounds (used by circle/line algorithms).
void plotPixel(int16_t x, int16_t y, uint16_t rgb565);
// Helper: fill horizontal span, clamped to screen.
void hspan(int16_t x, int16_t y, int16_t len, uint16_t rgb565);
// Octant helper for circle algorithms.
void circleOctants(int16_t cx, int16_t cy, int16_t x, int16_t y,
uint16_t rgb565, bool fill);
// Arc pixel helper - plots or fills one pixel/column depending on fill flag.
void arcImpl(int16_t cx, int16_t cy, int16_t r0, int16_t r1,
float startDeg, float endDeg, uint16_t rgb565, bool fill);
// ESP32 I2C driver buffer is 256 bytes. With 1 command byte, 255 bytes remain.
// 255 / 2 bytes per pixel = 127, rounded down to 126 for alignment.
static constexpr uint16_t CHUNK_PIXELS = 126;
};