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
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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;
|
||||
};
|
||||
Reference in New Issue
Block a user