ST7789/PicoGraphics: Templated framebuffer formats.

This commit is contained in:
Phil Howard 2022-06-06 17:54:15 +01:00
parent dbed4a463f
commit 7e4725d1cd
10 changed files with 460 additions and 256 deletions

View File

@ -9,4 +9,4 @@ target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_include_directories(st7789 INTERFACE ${CMAKE_CURRENT_LIST_DIR}) target_include_directories(st7789 INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need # Pull in pico libraries that we need
target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib pimoroni_bus hardware_spi hardware_pwm hardware_dma) target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib pimoroni_bus hardware_spi hardware_pwm hardware_dma pico_graphics)

View File

@ -200,7 +200,102 @@ namespace pimoroni {
gpio_put(cs, 1); gpio_put(cs, 1);
} }
void ST7789::update(PicoGraphics<PenRGB565> *graphics) {
command(reg::RAMWR, width * height * sizeof(uint16_t), (const char*)graphics->get_data());
}
void ST7789::update(PicoGraphics<PenRGB332> *graphics) {
uint8_t command = reg::RAMWR;
gpio_put(dc, 0); // command mode
gpio_put(cs, 0);
if(spi) {
spi_write_blocking(spi, &command, 1);
} else {
write_blocking_parallel(&command, 1);
}
gpio_put(dc, 1); // data mode
uint16_t row_buf[width];
for(auto y = 0u; y < height; y++) {
graphics->get_data(y, &row_buf);
// TODO: Add DMA->SPI / PIO while we prep the next row
if(spi) {
spi_write_blocking(spi, (const uint8_t*)row_buf, width * sizeof(uint16_t));
} else {
write_blocking_parallel((const uint8_t*)row_buf, width * sizeof(uint16_t));
}
}
gpio_put(cs, 1);
}
void ST7789::update(PicoGraphics<PenP8> *graphics) {
uint8_t command = reg::RAMWR;
gpio_put(dc, 0); // command mode
gpio_put(cs, 0);
if(spi) {
spi_write_blocking(spi, &command, 1);
} else {
write_blocking_parallel(&command, 1);
}
gpio_put(dc, 1); // data mode
uint16_t row_buf[width];
for(auto y = 0u; y < height; y++) {
graphics->get_data(y, &row_buf);
// TODO: Add DMA->SPI / PIO while we prep the next row
if(spi) {
spi_write_blocking(spi, (const uint8_t*)row_buf, width * sizeof(uint16_t));
} else {
write_blocking_parallel((const uint8_t*)row_buf, width * sizeof(uint16_t));
}
}
gpio_put(cs, 1);
}
void ST7789::update(PicoGraphics<PenP4> *graphics) {
uint8_t command = reg::RAMWR;
gpio_put(dc, 0); // command mode
gpio_put(cs, 0);
if(spi) {
spi_write_blocking(spi, &command, 1);
} else {
write_blocking_parallel(&command, 1);
}
gpio_put(dc, 1); // data mode
uint16_t row_buf[width];
for(auto y = 0u; y < height; y++) {
graphics->get_data(y, &row_buf);
// TODO: Add DMA->SPI / PIO while we prep the next row
if(spi) {
spi_write_blocking(spi, (const uint8_t*)row_buf, width * sizeof(uint16_t));
} else {
write_blocking_parallel((const uint8_t*)row_buf, width * sizeof(uint16_t));
}
}
gpio_put(cs, 1);
}
/*
// Native 16-bit framebuffer update // Native 16-bit framebuffer update
void ST7789::update() { void ST7789::update() {
command(reg::RAMWR, width * height * sizeof(uint16_t), (const char*)frame_buffer); command(reg::RAMWR, width * height * sizeof(uint16_t), (const char*)frame_buffer);
@ -238,6 +333,7 @@ namespace pimoroni {
gpio_put(cs, 1); gpio_put(cs, 1);
} }
*/
void ST7789::set_backlight(uint8_t brightness) { void ST7789::set_backlight(uint8_t brightness) {
// gamma correct the provided 0-255 brightness value onto a // gamma correct the provided 0-255 brightness value onto a

View File

@ -5,6 +5,7 @@
#include "hardware/pwm.h" #include "hardware/pwm.h"
#include "common/pimoroni_common.hpp" #include "common/pimoroni_common.hpp"
#include "common/pimoroni_bus.hpp" #include "common/pimoroni_bus.hpp"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include <algorithm> #include <algorithm>
@ -41,15 +42,11 @@ namespace pimoroni {
public: public:
// frame buffer where pixel data is stored
void *frame_buffer;
// Parallel init // Parallel init
ST7789(uint16_t width, uint16_t height, Rotation rotation, void *frame_buffer, ParallelPins pins) : ST7789(uint16_t width, uint16_t height, Rotation rotation, void *frame_buffer, ParallelPins pins) :
spi(nullptr), spi(nullptr),
width(width), height(height), rotation(rotation), round(false), width(width), height(height), rotation(rotation), round(false),
cs(pins.cs), dc(pins.dc), wr_sck(pins.wr_sck), rd_sck(pins.rd_sck), d0(pins.d0), bl(pins.bl), frame_buffer(frame_buffer) { cs(pins.cs), dc(pins.dc), wr_sck(pins.wr_sck), rd_sck(pins.rd_sck), d0(pins.d0), bl(pins.bl) {
gpio_set_function(wr_sck, GPIO_FUNC_SIO); gpio_set_function(wr_sck, GPIO_FUNC_SIO);
gpio_set_dir(wr_sck, GPIO_OUT); gpio_set_dir(wr_sck, GPIO_OUT);
@ -71,7 +68,7 @@ namespace pimoroni {
ST7789(uint16_t width, uint16_t height, Rotation rotation, bool round, void *frame_buffer, SPIPins pins) : ST7789(uint16_t width, uint16_t height, Rotation rotation, bool round, void *frame_buffer, SPIPins pins) :
spi(pins.spi), spi(pins.spi),
width(width), height(height), rotation(rotation), round(round), width(width), height(height), rotation(rotation), round(round),
cs(pins.cs), dc(pins.dc), wr_sck(pins.sck), d0(pins.mosi), bl(pins.bl), frame_buffer(frame_buffer) { cs(pins.cs), dc(pins.dc), wr_sck(pins.sck), d0(pins.mosi), bl(pins.bl) {
// configure spi interface and pins // configure spi interface and pins
spi_init(spi, SPI_BAUD); spi_init(spi, SPI_BAUD);
@ -86,17 +83,15 @@ namespace pimoroni {
void command(uint8_t command, size_t len = 0, const char *data = NULL); void command(uint8_t command, size_t len = 0, const char *data = NULL);
void set_backlight(uint8_t brightness); void set_backlight(uint8_t brightness);
void update(); void update(PicoGraphics<PenRGB565> *graphics);
void update(uint16_t *palette); void update(PicoGraphics<PenRGB332> *graphics);
void update(PicoGraphics<PenP8> *graphics);
void update(PicoGraphics<PenP4> *graphics);
private: private:
void configure_display(Rotation rotate); void configure_display(Rotation rotate);
void write_blocking_parallel(const uint8_t *src, size_t len); void write_blocking_parallel(const uint8_t *src, size_t len);
void common_init() { void common_init() {
if(!this->frame_buffer) {
this->frame_buffer = new uint8_t[width * height];
}
gpio_set_function(dc, GPIO_FUNC_SIO); gpio_set_function(dc, GPIO_FUNC_SIO);
gpio_set_dir(dc, GPIO_OUT); gpio_set_dir(dc, GPIO_OUT);

View File

@ -1,135 +1,40 @@
#include "pico_graphics.hpp" #include "pico_graphics.hpp"
namespace pimoroni { namespace pimoroni {
PicoGraphics::PicoGraphics(uint16_t width, uint16_t height, void *frame_buffer)
: frame_buffer((Pen *)frame_buffer), bounds(0, 0, width, height), clip(0, 0, width, height) {
set_font(&font6);
set_palette_mode(PaletteModeRGB332);
};
void PicoGraphics::set_font(const bitmap::font_t *font){ template<class T>
void PicoGraphics<T>::set_font(const bitmap::font_t *font){
this->font = font; this->font = font;
} }
void PicoGraphics::set_pen(Pen p) { template<class T>
pen = p; void PicoGraphics<T>::set_pen(uint16_t p) {
pen.set_color(p);
} }
void PicoGraphics::set_palette_mode(PaletteMode mode) { template<class T>
palette_mode = mode; void PicoGraphics<T>::set_clip(const Rect &r) {
if(mode == PaletteModeRGB332) {
rgb332_palette();
} else {
empty_palette();
}
}
int PicoGraphics::create_pen(uint8_t r, uint8_t g, uint8_t b) {
if (palette_mode == PaletteModeRGB332) {
return rgb_to_rgb332_index(r, g, b); // Fast pack RGB into palette index
} else {
RGB565 c = create_pen_rgb565(r, g, b);
int result = search_palette(c);
if (result == -1) {
result = put_palette(create_pen_rgb565(r, g, b));
}
return result;
}
}
void PicoGraphics::set_pen(uint8_t r, uint8_t g, uint8_t b) {
int result = create_pen(r, g, b);
(void)result;
}
int PicoGraphics::search_palette(RGB565 c) {
for(auto i = 0u; i < 256; i++) {
if((palette_status[i] & PaletteStatusUsed) && palette[i] == c) return i;
}
return -1;
}
int PicoGraphics::put_palette(RGB565 c) {
if(palette_mode != PaletteModeUSER) return -1;
for(auto i = 0u; i < 256; i++) {
if(!(palette_status[i] & (PaletteStatusUsed | PaletteStatusReserved))) {
palette[i] = c;
palette_status[i] = PaletteStatusUsed;
return i;
}
}
return -1;
}
void PicoGraphics::set_palette(uint8_t i, RGB565 c) {
if(palette_mode != PaletteModeUSER) return;
palette[i] = c;
palette_status[i] |= PaletteStatusUsed;
}
int PicoGraphics::reserve_palette() {
if(palette_mode != PaletteModeUSER) return - 1;
for (auto i = 0u; i < 256; i++) {
if (!palette_status[i]) {
palette_status[i] = PaletteStatusReserved;
return i;
}
}
return -1;
}
void PicoGraphics::empty_palette() {
for (auto i = 0u; i < 256; i++) {
palette[i] = 0;
palette_status[i] = 0;
}
}
void PicoGraphics::rgb332_palette() {
for (auto i = 0u; i < 256; i++) {
// Convert the implicit RGB332 (i) into RGB565
// 0b11100 000 0b00011100 0b00000011
palette[i] = ((i & 0b11100000) << 8) | ((i & 0b00011100) << 6) | ((i & 0b00000011) << 3);
palette[i] = __builtin_bswap16(palette[i]);
palette_status[i] = PaletteStatusUsed;
}
}
void PicoGraphics::set_clip(const Rect &r) {
clip = bounds.intersection(r); clip = bounds.intersection(r);
} }
void PicoGraphics::remove_clip() { template<class T>
void PicoGraphics<T>::remove_clip() {
clip = bounds; clip = bounds;
} }
Pen* PicoGraphics::ptr(const Rect &r) { template<class T>
return frame_buffer + r.x + r.y * bounds.w; void PicoGraphics<T>::clear() {
}
Pen* PicoGraphics::ptr(const Point &p) {
return frame_buffer + p.x + p.y * bounds.w;
}
Pen* PicoGraphics::ptr(int32_t x, int32_t y) {
return frame_buffer + x + y * bounds.w;
}
void PicoGraphics::clear() {
rectangle(clip); rectangle(clip);
} }
void PicoGraphics::pixel(const Point &p) { template<class T>
void PicoGraphics<T>::pixel(const Point &p) {
if(!clip.contains(p)) return; if(!clip.contains(p)) return;
*ptr(p) = pen; pen.set_pixel(frame_buffer, p.x, p.y, bounds.w);
} }
void PicoGraphics::pixel_span(const Point &p, int32_t l) { template<class T>
void PicoGraphics<T>::pixel_span(const Point &p, int32_t l) {
// check if span in bounds // check if span in bounds
if( p.x + l < clip.x || p.x >= clip.x + clip.w || if( p.x + l < clip.x || p.x >= clip.x + clip.w ||
p.y < clip.y || p.y >= clip.y + clip.h) return; p.y < clip.y || p.y >= clip.y + clip.h) return;
@ -139,31 +44,36 @@ namespace pimoroni {
if(clipped.x < clip.x) {l += clipped.x - clip.x; clipped.x = clip.x;} if(clipped.x < clip.x) {l += clipped.x - clip.x; clipped.x = clip.x;}
if(clipped.x + l >= clip.x + clip.w) {l = clip.x + clip.w - clipped.x;} if(clipped.x + l >= clip.x + clip.w) {l = clip.x + clip.w - clipped.x;}
Pen *dest = ptr(clipped); Point dest(clipped.x, clipped.y);
while(l--) { while(l--) {
*dest++ = pen; pen.set_pixel(frame_buffer, dest.x, dest.y, bounds.w);
dest.x++;
} }
} }
void PicoGraphics::rectangle(const Rect &r) { template<class T>
void PicoGraphics<T>::rectangle(const Rect &r) {
// clip and/or discard depending on rectangle visibility // clip and/or discard depending on rectangle visibility
Rect clipped = r.intersection(clip); Rect clipped = r.intersection(clip);
if(clipped.empty()) return; if(clipped.empty()) return;
Pen *dest = ptr(clipped); Point dest(clipped.x, clipped.y);
while(clipped.h--) { while(clipped.h--) {
// draw span of pixels for this row // draw span of pixels for this row
for(int32_t i = 0; i < clipped.w; i++) { pixel_span(dest, clipped.w);
*dest++ = pen; /*for(int32_t i = 0; i < clipped.w; i++) {
} pen.set_pixel(frame_buffer, dest.x, dest.y, bounds.w);
dest.x++;
}*/
// move to next scanline // move to next scanline
dest += bounds.w - clipped.w; dest.y++;
} }
} }
void PicoGraphics::circle(const Point &p, int32_t radius) { template<class T>
void PicoGraphics<T>::circle(const Point &p, int32_t radius) {
// circle in screen bounds? // circle in screen bounds?
Rect bounds = Rect(p.x - radius, p.y - radius, radius * 2, radius * 2); Rect bounds = Rect(p.x - radius, p.y - radius, radius * 2, radius * 2);
if(!bounds.intersects(clip)) return; if(!bounds.intersects(clip)) return;
@ -191,19 +101,22 @@ namespace pimoroni {
} }
} }
void PicoGraphics::character(const char c, const Point &p, uint8_t scale) { template<class T>
void PicoGraphics<T>::character(const char c, const Point &p, uint8_t scale) {
bitmap::character(font, [this](int32_t x, int32_t y, int32_t w, int32_t h){ bitmap::character(font, [this](int32_t x, int32_t y, int32_t w, int32_t h){
rectangle(Rect(x, y, w, h)); rectangle(Rect(x, y, w, h));
}, c, p.x, p.y, scale); }, c, p.x, p.y, scale);
} }
void PicoGraphics::text(const std::string &t, const Point &p, int32_t wrap, uint8_t scale) { template<class T>
void PicoGraphics<T>::text(const std::string &t, const Point &p, int32_t wrap, uint8_t scale) {
bitmap::text(font, [this](int32_t x, int32_t y, int32_t w, int32_t h){ bitmap::text(font, [this](int32_t x, int32_t y, int32_t w, int32_t h){
rectangle(Rect(x, y, w, h)); rectangle(Rect(x, y, w, h));
}, t, p.x, p.y, wrap, scale); }, t, p.x, p.y, wrap, scale);
} }
int32_t PicoGraphics::measure_text(const std::string &t, uint8_t scale) { template<class T>
int32_t PicoGraphics<T>::measure_text(const std::string &t, uint8_t scale) {
return bitmap::measure_text(font, t, scale); return bitmap::measure_text(font, t, scale);
} }
@ -215,7 +128,8 @@ namespace pimoroni {
return (p1.y == p2.y && p1.x > p2.x) || (p1.y < p2.y); return (p1.y == p2.y && p1.x > p2.x) || (p1.y < p2.y);
} }
void PicoGraphics::triangle(Point p1, Point p2, Point p3) { template<class T>
void PicoGraphics<T>::triangle(Point p1, Point p2, Point p3) {
Rect triangle_bounds( Rect triangle_bounds(
Point(std::min(p1.x, std::min(p2.x, p3.x)), std::min(p1.y, std::min(p2.y, p3.y))), Point(std::min(p1.x, std::min(p2.x, p3.x)), std::min(p1.y, std::min(p2.y, p3.y))),
Point(std::max(p1.x, std::max(p2.x, p3.x)), std::max(p1.y, std::max(p2.y, p3.y)))); Point(std::max(p1.x, std::max(p2.x, p3.x)), std::max(p1.y, std::max(p2.y, p3.y))));
@ -257,13 +171,13 @@ namespace pimoroni {
int32_t w1 = w1row; int32_t w1 = w1row;
int32_t w2 = w2row; int32_t w2 = w2row;
Pen *dest = ptr(triangle_bounds.x, triangle_bounds.y + y); Point dest = Point(triangle_bounds.x, triangle_bounds.y + y);
for (int32_t x = 0; x < triangle_bounds.w; x++) { for (int32_t x = 0; x < triangle_bounds.w; x++) {
if ((w0 | w1 | w2) >= 0) { if ((w0 | w1 | w2) >= 0) {
*dest = pen; pen.set_pixel(frame_buffer, dest.x, dest.y, bounds.w);
} }
dest++; dest.x++;
w0 += a12; w0 += a12;
w1 += a20; w1 += a20;
@ -276,7 +190,8 @@ namespace pimoroni {
} }
} }
void PicoGraphics::polygon(const std::vector<Point> &points) { template<class T>
void PicoGraphics<T>::polygon(const std::vector<Point> &points) {
static int32_t nodes[64]; // maximum allowed number of nodes per scanline for polygon rendering static int32_t nodes[64]; // maximum allowed number of nodes per scanline for polygon rendering
int32_t miny = points[0].y, maxy = points[0].y; int32_t miny = points[0].y, maxy = points[0].y;
@ -322,7 +237,8 @@ namespace pimoroni {
} }
} }
void PicoGraphics::line(Point p1, Point p2) { template<class T>
void PicoGraphics<T>::line(Point p1, Point p2) {
// fast horizontal line // fast horizontal line
if(p1.y == p2.y) { if(p1.y == p2.y) {
int32_t start = std::max(clip.x, std::min(p1.x, p2.x)); int32_t start = std::max(clip.x, std::min(p1.x, p2.x));
@ -335,10 +251,10 @@ namespace pimoroni {
if(p1.x == p2.x) { if(p1.x == p2.x) {
int32_t start = std::max(clip.y, std::min(p1.y, p2.y)); int32_t start = std::max(clip.y, std::min(p1.y, p2.y));
int32_t length = std::min(clip.y + clip.h, std::max(p1.y, p2.y)) - start; int32_t length = std::min(clip.y + clip.h, std::max(p1.y, p2.y)) - start;
Pen *dest = ptr(p1.x, start); Point dest(p1.x, start);
while(length--) { while(length--) {
*dest = pen; pen.set_pixel(frame_buffer, dest.x, dest.y, bounds.w);
dest += bounds.w; dest.y++;
} }
return; return;
} }
@ -357,7 +273,7 @@ namespace pimoroni {
int32_t x = p1.x; int32_t x = p1.x;
int32_t y = p1.y << 16; int32_t y = p1.y << 16;
while(s--) { while(s--) {
pixel(Point(x, y >> 16)); pen.set_pixel(frame_buffer, x, y >> 16, bounds.w);
y += sy; y += sy;
x += sx; x += sx;
} }
@ -369,7 +285,7 @@ namespace pimoroni {
int32_t y = p1.y; int32_t y = p1.y;
int32_t x = p1.x << 16; int32_t x = p1.x << 16;
while(s--) { while(s--) {
pixel(Point(x >> 16, y)); pen.set_pixel(frame_buffer, x >> 16, y, bounds.w);
y += sy; y += sy;
x += sx; x += sx;
} }

View File

@ -9,9 +9,9 @@
// a tiny little graphics library for our Pico products // a tiny little graphics library for our Pico products
// supports only 16-bit (565) RGB framebuffers // supports only 16-bit (565) RGB framebuffers
namespace pimoroni { namespace pimoroni {
typedef uint8_t RGB332;
typedef uint8_t Pen;
typedef uint16_t RGB565; typedef uint16_t RGB565;
typedef int Pen;
struct Rect; struct Rect;
@ -44,59 +44,255 @@ namespace pimoroni {
void deflate(int32_t v); void deflate(int32_t v);
}; };
class PicoGraphicsPenType {
public:
struct PaletteEntry {
RGB565 color;
bool used;
};
static constexpr RGB332 rgb_to_rgb332(uint8_t r, uint8_t g, uint8_t b) {
return (r & 0b11100000) | ((g & 0b11100000) >> 3) | ((b & 0b11000000) >> 6);
}
static constexpr RGB565 rgb332_to_rgb565(RGB332 c) {
uint16_t p = ((c & 0b11100000) << 8) |
((c & 0b00011100) << 6) |
((c & 0b00000011) << 3);
return __builtin_bswap16(p);
}
static constexpr RGB565 rgb_to_rgb565(uint8_t r, uint8_t g, uint8_t b) {
uint16_t p = ((r & 0b11111000) << 8) |
((g & 0b11111100) << 3) |
((b & 0b11111000) >> 3);
return __builtin_bswap16(p);
}
virtual void set_pixel(void *frame_buffer, uint x, uint y, uint stride);
virtual int create(uint8_t r, uint8_t g, uint8_t b);
virtual void set_color(uint c);
virtual void set_color(uint8_t r, uint8_t g, uint8_t b);
virtual void update_color(uint8_t i, uint8_t r, uint8_t g, uint8_t b);
virtual void palette_lookup(void *frame_buffer, void *result, uint offset, uint length);
static size_t buffer_size(uint w, uint h); // Must be static, since user must know required size to alloc framebuffer
};
class PenP4 : public PicoGraphicsPenType {
public:
uint8_t color;
PaletteEntry palette[8];
PenP4() {
palette[0].color = rgb_to_rgb565(57, 48, 57); // Black
palette[1].color = rgb_to_rgb565(255, 255, 255); // White
palette[2].color = rgb_to_rgb565(58, 91, 70); // Green
palette[3].color = rgb_to_rgb565(61, 59, 94); // Blue
palette[4].color = rgb_to_rgb565(156, 72, 75); // Red
palette[5].color = rgb_to_rgb565(208, 190, 71); // Yellow
palette[6].color = rgb_to_rgb565(177, 106, 73); // Orange
palette[7].color = rgb_to_rgb565(255, 255, 255); // Clear
}
void set_color(uint c) {
color = c & 0xf;
}
void set_color(uint8_t r, uint8_t g, uint8_t b) override {
// TODO look up closest palette colour, or just NOOP?
}
void update_color(uint8_t i, uint8_t r, uint8_t g, uint8_t b) {}
void reset_color(uint8_t i) {}
int create(uint8_t r, uint8_t g, uint8_t b) override {
return -1; // Can never create new colours, fixed palette!
}
void set_pixel(void *frame_buffer, uint x, uint y, uint stride) override {
// pointer to byte in framebuffer that contains this pixel
uint8_t *buf = (uint8_t *)frame_buffer;
uint8_t *p = &buf[(x / 2) + (y * stride / 2)];
uint8_t o = (~x & 0b1) * 4; // bit offset within byte
uint8_t m = ~(0b1111 << o); // bit mask for byte
uint8_t b = color << o; // bit value shifted to position
*p &= m; // clear bits
*p |= b; // set value
}
void palette_lookup(void *frame_buffer, void *result, uint offset, uint length) override {
uint8_t *src = (uint8_t *)frame_buffer;
uint16_t *dst = (uint16_t *)result;
for(auto x = 0u; x < length; x++) {
uint8_t c = src[(offset / 2) + (x / 2)];
uint8_t o = (~x & 0b1) * 4; // bit offset within byte
uint8_t b = (c >> o) & 0xf; // bit value shifted to position
dst[x] = palette[b].color;
}
}
static size_t buffer_size(uint w, uint h) {
return w * h / 2;
}
};
class PenP8 : public PicoGraphicsPenType {
public:
uint8_t color;
PaletteEntry palette[256];
PenP8() {
for(auto i = 0u; i < 256; i++) {
reset_color(i);
}
}
void set_color(uint c) override {
color = c;
}
void set_color(uint8_t r, uint8_t g, uint8_t b) override {
// TODO look up closest palette colour, or just NOOP?
}
void update_color(uint8_t i, uint8_t r, uint8_t g, uint8_t b) {
palette[i].color = rgb_to_rgb565(r, g, b);
palette[i].used = true;
}
void reset_color(uint8_t i) {
palette[i].color = 0;
palette[i].used = false;
}
int create(uint8_t r, uint8_t g, uint8_t b) override {
// Create a colour and place it in the palette if there's space
RGB565 c = rgb_to_rgb565(r, g, b);
for(auto i = 0u; i < 256u; i++) {
if(!palette[i].used) {
palette[i].color = c;
palette[i].used = true;
return i;
}
}
return -1;
}
void set_pixel(void *frame_buffer, uint x, uint y, uint stride) override {
uint8_t *buf = (uint8_t *)frame_buffer;
buf[y * stride + x] = color;
}
void palette_lookup(void *frame_buffer, void *result, uint offset, uint length) override {
uint8_t *src = (uint8_t *)frame_buffer;
uint16_t *dst = (uint16_t *)result;
for(auto x = 0u; x < length; x++) {
dst[x] = palette[src[offset + x]].color;
}
}
static size_t buffer_size(uint w, uint h) {
return w * h;
}
};
class PenRGB332 : public PicoGraphicsPenType {
public:
RGB332 color;
PaletteEntry palette[256];
PenRGB332() {
for(auto i = 0u; i < 256; i++) {
reset_color(i);
}
}
void set_color(uint c) {
color = c;
}
void set_color(uint8_t r, uint8_t g, uint8_t b) override {
color = rgb_to_rgb332(r, g, b);
}
void update_color(uint8_t i, uint8_t r, uint8_t g, uint8_t b) {
palette[i].color = rgb_to_rgb565(r, g, b);
palette[i].used = true;
}
void reset_color(uint8_t i) {
palette[i].color = rgb332_to_rgb565(i);
palette[i].used = true;
}
int create(uint8_t r, uint8_t g, uint8_t b) override {
return rgb_to_rgb332(r, g, b);
}
void set_pixel(void *frame_buffer, uint x, uint y, uint stride) override {
uint8_t *buf = (uint8_t *)frame_buffer;
buf[y * stride + x] = color;
}
void palette_lookup(void *frame_buffer, void *result, uint offset, uint length) override {
uint8_t *src = (uint8_t *)frame_buffer;
uint16_t *dst = (uint16_t *)result;
for(auto x = 0u; x < length; x++) {
dst[x] = palette[src[offset + x]].color;
}
}
static size_t buffer_size(uint w, uint h) {
return w * h;
}
};
class PenRGB565 : public PicoGraphicsPenType {
public:
uint16_t color;
void set_color(uint c) override {
color = c;
}
void update_color(uint8_t i, uint8_t r, uint8_t g, uint8_t b) {
// Palette only. Does nothing.
}
void reset_color(uint8_t i) {
// Palette only. Does nothing.
}
void set_color(uint8_t r, uint8_t g, uint8_t b) override {
color = rgb_to_rgb565(r, g, b);
}
int create(uint8_t r, uint8_t g, uint8_t b) override {
return rgb_to_rgb565(r, g, b);
}
void set_pixel(void *frame_buffer, uint x, uint y, uint stride) override {
uint16_t *buf = (uint16_t *)frame_buffer;
buf[y * stride + x] = color;
}
void palette_lookup(void *frame_buffer, void *result, uint offset, uint length) override {
}
static size_t buffer_size(uint w, uint h) {
return w * h * sizeof(RGB565);
}
};
template <class T=PicoGraphicsPenType>
class PicoGraphics { class PicoGraphics {
public: public:
Pen *frame_buffer; void *frame_buffer;
Rect bounds; Rect bounds;
Rect clip; Rect clip;
Pen pen;
const bitmap::font_t *font; const bitmap::font_t *font;
RGB565 palette[256]; T pen;
uint8_t palette_status[256];
enum PaletteMode {
PaletteModeRGB332 = 0,
PaletteModeUSER = 1
};
enum PaletteStatus : uint8_t {
PaletteStatusReserved = 1,
PaletteStatusUsed = 2
};
PaletteMode palette_mode = PaletteModeRGB332;
public: public:
PicoGraphics(uint16_t width, uint16_t height, void *frame_buffer); PicoGraphics(uint16_t width, uint16_t height, void *frame_buffer)
: frame_buffer(frame_buffer), bounds(0, 0, width, height), clip(0, 0, width, height) {
set_font(&font6);
if(frame_buffer == nullptr) {
frame_buffer = (void *)(new uint8_t[pen.buffer_size(width, height)]);
}
};
void set_font(const bitmap::font_t *font); void set_font(const bitmap::font_t *font);
void set_pen(Pen p); void set_pen(uint16_t p);
void set_palette_mode(PaletteMode mode);
[[deprecated("Use uint8_t create_pen(uint8_t, uint8_t, uint8_t).")]] int create_pen(uint8_t r, uint8_t g, uint8_t b) {
void set_pen(uint8_t r, uint8_t g, uint8_t b); return pen.create(r, g, b);
static constexpr Pen rgb_to_rgb332_index(uint8_t r, uint8_t g, uint8_t b) {
return (r & 0b11100000) | ((g & 0b11100000) >> 3) | ((b & 0b11000000) >> 6);
} }
static constexpr RGB565 create_pen_rgb565(uint8_t r, uint8_t g, uint8_t b) { void set_pen(uint8_t r, uint8_t g, uint8_t b) {
uint16_t p = ((r & 0b11111000) << 8) | pen.set_color(r, g, b);
((g & 0b11111100) << 3) |
((b & 0b11111000) >> 3);
return __builtin_bswap16(p);
} }
static constexpr RGB565 create_pen_rgb332(uint8_t r, uint8_t g, uint8_t b) {
uint16_t p = ((r & 0b11100000) << 8) |
((g & 0b11100000) << 3) |
((b & 0b11000000) >> 3);
return __builtin_bswap16(p); void update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) {
pen.update_color(i, r, g, b);
}
void reset_pen(uint8_t i) {
pen.reset_color(i);
} }
void set_dimensions(int width, int height) { void set_dimensions(int width, int height) {
@ -106,23 +302,16 @@ namespace pimoroni {
clip.h = height; clip.h = height;
} }
int create_pen(uint8_t r, uint8_t g, uint8_t b);
int reserve_palette();
void empty_palette();
void rgb332_palette();
int search_palette(RGB565 c);
int get_palette(uint8_t i);
void set_palette(uint8_t i, RGB565 c);
int put_palette(RGB565 c);
void set_clip(const Rect &r); void set_clip(const Rect &r);
void remove_clip(); void remove_clip();
Pen* ptr(const Point &p); void* get_data() {
Pen* ptr(const Rect &r); return frame_buffer;
Pen* ptr(int32_t x, int32_t y); }
void get_data(uint y, void *row_buf) {
pen.palette_lookup(frame_buffer, row_buf, y * bounds.w, bounds.w);
}
void clear(); void clear();
void pixel(const Point &p); void pixel(const Point &p);
@ -137,4 +326,9 @@ namespace pimoroni {
void line(Point p1, Point p2); void line(Point p1, Point p2);
}; };
template class PicoGraphics<PenP4>;
template class PicoGraphics<PenP8>;
template class PicoGraphics<PenRGB332>;
template class PicoGraphics<PenRGB565>;
} }

View File

@ -5,11 +5,5 @@
namespace pimoroni { namespace pimoroni {
void PicoGraphicsST7789::update() {
st7789.update(palette);
}
void PicoGraphicsST7789::set_backlight(uint8_t brightness) {
st7789.set_backlight(brightness);
}
} }

View File

@ -6,42 +6,46 @@
namespace pimoroni { namespace pimoroni {
class PicoGraphicsST7789 : public PicoGraphics { template <class T=PicoGraphicsPenType>
class PicoGraphicsST7789 : public PicoGraphics<T> {
private: private:
ST7789 st7789; ST7789 st7789;
public: public:
PicoGraphicsST7789(uint16_t width, uint16_t height, Rotation rotation, bool round=false, void *frame_buffer=nullptr) : PicoGraphicsST7789(uint16_t width, uint16_t height, Rotation rotation, bool round=false, void *frame_buffer=nullptr) :
PicoGraphics(width, height, frame_buffer), PicoGraphics<T>(width, height, frame_buffer),
st7789(width, height, rotation, round, frame_buffer, get_spi_pins(BG_SPI_FRONT)) { st7789(width, height, rotation, round, frame_buffer, get_spi_pins(BG_SPI_FRONT)) {
common_init(); common_init();
}; };
PicoGraphicsST7789(uint16_t width, uint16_t height, Rotation rotation, bool round, void *frame_buffer, SPIPins bus_pins) : PicoGraphicsST7789(uint16_t width, uint16_t height, Rotation rotation, bool round, void *frame_buffer, SPIPins bus_pins) :
PicoGraphics(width, height, frame_buffer), PicoGraphics<T>(width, height, frame_buffer),
st7789(width, height, rotation, round, frame_buffer, bus_pins) { st7789(width, height, rotation, round, frame_buffer, bus_pins) {
common_init(); common_init();
}; };
PicoGraphicsST7789(uint16_t width, uint16_t height, Rotation rotation, void *frame_buffer, ParallelPins bus_pins) : PicoGraphicsST7789(uint16_t width, uint16_t height, Rotation rotation, void *frame_buffer, ParallelPins bus_pins) :
PicoGraphics(width, height, frame_buffer), PicoGraphics<T>(width, height, frame_buffer),
st7789(width, height, rotation, frame_buffer, bus_pins) { st7789(width, height, rotation, frame_buffer, bus_pins) {
common_init(); common_init();
}; };
void common_init() { void common_init() {
this->frame_buffer = (Pen *)st7789.frame_buffer; st7789.init();
this->st7789.init(); this->set_dimensions(st7789.width, st7789.height);
this->set_dimensions(this->st7789.width, this->st7789.height); st7789.update(this);
this->st7789.update(palette); }
void update() {
st7789.update(this);
}
void set_backlight(uint8_t brightness) {
st7789.set_backlight(brightness);
} }
void update();
void set_backlight(uint8_t brightness);
void configure_display(bool rotate180);
void set_framebuffer(void* frame_buffer) { void set_framebuffer(void* frame_buffer) {
this->frame_buffer = (Pen *)frame_buffer; this->frame_buffer = frame_buffer;
st7789.frame_buffer = frame_buffer;
} }
}; };

View File

@ -10,9 +10,8 @@ MP_DEFINE_CONST_FUN_OBJ_2(GenericST7789_set_backlight_obj, GenericST7789_set_bac
MP_DEFINE_CONST_FUN_OBJ_2(GenericST7789_set_framebuffer_obj, GenericST7789_set_framebuffer); MP_DEFINE_CONST_FUN_OBJ_2(GenericST7789_set_framebuffer_obj, GenericST7789_set_framebuffer);
// Palette management // Palette management
MP_DEFINE_CONST_FUN_OBJ_2(GenericST7789_set_palette_mode_obj, GenericST7789_set_palette_mode); MP_DEFINE_CONST_FUN_OBJ_KW(GenericST7789_update_pen_obj, 4, GenericST7789_update_pen);
MP_DEFINE_CONST_FUN_OBJ_3(GenericST7789_set_palette_obj, GenericST7789_set_palette); MP_DEFINE_CONST_FUN_OBJ_2(GenericST7789_reset_pen_obj, GenericST7789_reset_pen);
MP_DEFINE_CONST_FUN_OBJ_1(GenericST7789_reserve_palette_obj, GenericST7789_reserve_palette);
// Pen // Pen
MP_DEFINE_CONST_FUN_OBJ_2(GenericST7789_set_pen_obj, GenericST7789_set_pen); MP_DEFINE_CONST_FUN_OBJ_2(GenericST7789_set_pen_obj, GenericST7789_set_pen);
@ -41,9 +40,8 @@ STATIC const mp_rom_map_elem_t GenericST7789_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_set_pen), MP_ROM_PTR(&GenericST7789_set_pen_obj) }, { MP_ROM_QSTR(MP_QSTR_set_pen), MP_ROM_PTR(&GenericST7789_set_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&GenericST7789_update_obj) }, { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&GenericST7789_update_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_palette_mode), MP_ROM_PTR(&GenericST7789_set_palette_mode_obj) }, { MP_ROM_QSTR(MP_QSTR_update_pen), MP_ROM_PTR(&GenericST7789_update_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_palette), MP_ROM_PTR(&GenericST7789_set_palette_obj) }, { MP_ROM_QSTR(MP_QSTR_reset_pen), MP_ROM_PTR(&GenericST7789_reset_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_reserve_palette), MP_ROM_PTR(&GenericST7789_reserve_palette_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_backlight), MP_ROM_PTR(&GenericST7789_set_backlight_obj) }, { MP_ROM_QSTR(MP_QSTR_set_backlight), MP_ROM_PTR(&GenericST7789_set_backlight_obj) },
{ MP_ROM_QSTR(MP_QSTR_create_pen), MP_ROM_PTR(&GenericST7789_create_pen_obj) }, { MP_ROM_QSTR(MP_QSTR_create_pen), MP_ROM_PTR(&GenericST7789_create_pen_obj) },

View File

@ -4,7 +4,9 @@
#include "micropython/modules/util.hpp" #include "micropython/modules/util.hpp"
#ifndef PICO_GRAPHICS_PEN_TYPE
#define PICO_GRAPHICS_PEN_TYPE PenP4
#endif
using namespace pimoroni; using namespace pimoroni;
@ -14,7 +16,7 @@ extern "C" {
typedef struct _GenericST7789_obj_t { typedef struct _GenericST7789_obj_t {
mp_obj_base_t base; mp_obj_base_t base;
PicoGraphicsST7789 *st7789; PicoGraphicsST7789<PICO_GRAPHICS_PEN_TYPE> *st7789;
void *buffer; void *buffer;
} GenericST7789_obj_t; } GenericST7789_obj_t;
@ -66,32 +68,34 @@ mp_obj_t GenericST7789_make_new(const mp_obj_type_t *type, size_t n_args, size_t
break; break;
} }
size_t required_size = PICO_GRAPHICS_PEN_TYPE::buffer_size(width, height);
if (args[ARG_buffer].u_obj != mp_const_none) { if (args[ARG_buffer].u_obj != mp_const_none) {
mp_buffer_info_t bufinfo; mp_buffer_info_t bufinfo;
mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_RW); mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_RW);
self->buffer = bufinfo.buf; self->buffer = bufinfo.buf;
if(bufinfo.len < (size_t)(width * height * sizeof(Pen))) { if(bufinfo.len < (size_t)(required_size)) {
mp_raise_ValueError("Supplied buffer is too small!"); mp_raise_ValueError("Supplied buffer is too small!");
} }
} else { } else {
self->buffer = m_new(uint8_t, width * height * sizeof(Pen)); self->buffer = m_new(uint8_t, required_size);
} }
if (display == DISPLAY_TUFTY_2040) { if (display == DISPLAY_TUFTY_2040) {
if (args[ARG_bus].u_obj == mp_const_none) { if (args[ARG_bus].u_obj == mp_const_none) {
self->st7789 = m_new_class(PicoGraphicsST7789, width, height, rotate, self->buffer, {10, 11, 12, 13, 14, 2}); self->st7789 = m_new_class(PicoGraphicsST7789<PICO_GRAPHICS_PEN_TYPE>, width, height, rotate, self->buffer, {10, 11, 12, 13, 14, 2});
} else if (mp_obj_is_type(args[ARG_bus].u_obj, &ParallelPins_type)) { } else if (mp_obj_is_type(args[ARG_bus].u_obj, &ParallelPins_type)) {
_PimoroniBus_obj_t *bus = (_PimoroniBus_obj_t *)MP_OBJ_TO_PTR(args[ARG_bus].u_obj); _PimoroniBus_obj_t *bus = (_PimoroniBus_obj_t *)MP_OBJ_TO_PTR(args[ARG_bus].u_obj);
self->st7789 = m_new_class(PicoGraphicsST7789, width, height, rotate, self->buffer, *(ParallelPins *)(bus->pins)); self->st7789 = m_new_class(PicoGraphicsST7789<PICO_GRAPHICS_PEN_TYPE>, width, height, rotate, self->buffer, *(ParallelPins *)(bus->pins));
} else { } else {
mp_raise_ValueError("ParallelBus expected!"); mp_raise_ValueError("ParallelBus expected!");
} }
} else { } else {
if (args[ARG_bus].u_obj == mp_const_none) { if (args[ARG_bus].u_obj == mp_const_none) {
self->st7789 = m_new_class(PicoGraphicsST7789, width, height, rotate, round, self->buffer, get_spi_pins(BG_SPI_FRONT)); self->st7789 = m_new_class(PicoGraphicsST7789<PICO_GRAPHICS_PEN_TYPE>, width, height, rotate, round, self->buffer, get_spi_pins(BG_SPI_FRONT));
} else if (mp_obj_is_type(args[ARG_bus].u_obj, &SPIPins_type)) { } else if (mp_obj_is_type(args[ARG_bus].u_obj, &SPIPins_type)) {
_PimoroniBus_obj_t *bus = (_PimoroniBus_obj_t *)MP_OBJ_TO_PTR(args[ARG_bus].u_obj); _PimoroniBus_obj_t *bus = (_PimoroniBus_obj_t *)MP_OBJ_TO_PTR(args[ARG_bus].u_obj);
self->st7789 = m_new_class(PicoGraphicsST7789, width, height, rotate, round, self->buffer, *(SPIPins *)(bus->pins)); self->st7789 = m_new_class(PicoGraphicsST7789<PICO_GRAPHICS_PEN_TYPE>, width, height, rotate, round, self->buffer, *(SPIPins *)(bus->pins));
} else { } else {
mp_raise_ValueError("SPIBus expected!"); mp_raise_ValueError("SPIBus expected!");
} }
@ -146,7 +150,7 @@ mp_obj_t GenericST7789_set_backlight(mp_obj_t self_in, mp_obj_t brightness) {
} }
mp_obj_t GenericST7789_module_RGB332(mp_obj_t r, mp_obj_t g, mp_obj_t b) { mp_obj_t GenericST7789_module_RGB332(mp_obj_t r, mp_obj_t g, mp_obj_t b) {
return mp_obj_new_int(PicoGraphicsST7789::create_pen_rgb332( return mp_obj_new_int(PicoGraphicsPenType::rgb_to_rgb332(
mp_obj_get_int(r), mp_obj_get_int(r),
mp_obj_get_int(g), mp_obj_get_int(g),
mp_obj_get_int(b) mp_obj_get_int(b)
@ -154,7 +158,7 @@ mp_obj_t GenericST7789_module_RGB332(mp_obj_t r, mp_obj_t g, mp_obj_t b) {
} }
mp_obj_t GenericST7789_module_RGB565(mp_obj_t r, mp_obj_t g, mp_obj_t b) { mp_obj_t GenericST7789_module_RGB565(mp_obj_t r, mp_obj_t g, mp_obj_t b) {
return mp_obj_new_int(PicoGraphicsST7789::create_pen_rgb565( return mp_obj_new_int(PicoGraphicsPenType::rgb_to_rgb565(
mp_obj_get_int(r), mp_obj_get_int(r),
mp_obj_get_int(g), mp_obj_get_int(g),
mp_obj_get_int(b) mp_obj_get_int(b)
@ -164,40 +168,44 @@ mp_obj_t GenericST7789_module_RGB565(mp_obj_t r, mp_obj_t g, mp_obj_t b) {
mp_obj_t GenericST7789_set_pen(mp_obj_t self_in, mp_obj_t pen) { mp_obj_t GenericST7789_set_pen(mp_obj_t self_in, mp_obj_t pen) {
GenericST7789_obj_t *self = MP_OBJ_TO_PTR2(self_in, GenericST7789_obj_t); GenericST7789_obj_t *self = MP_OBJ_TO_PTR2(self_in, GenericST7789_obj_t);
self->st7789->set_pen(mp_obj_get_int(pen) & 0xff); self->st7789->set_pen(mp_obj_get_int(pen));
return mp_const_none; return mp_const_none;
} }
mp_obj_t GenericST7789_set_palette_mode(mp_obj_t self_in, mp_obj_t mode) { mp_obj_t GenericST7789_reset_pen(mp_obj_t self_in, mp_obj_t pen) {
GenericST7789_obj_t *self = MP_OBJ_TO_PTR2(self_in, GenericST7789_obj_t); GenericST7789_obj_t *self = MP_OBJ_TO_PTR2(self_in, GenericST7789_obj_t);
self->st7789->set_palette_mode((PicoGraphicsST7789::PaletteMode)mp_obj_get_int(mode)); self->st7789->reset_pen(mp_obj_get_int(pen));
return mp_const_none; return mp_const_none;
} }
mp_obj_t GenericST7789_set_palette(mp_obj_t self_in, mp_obj_t index, mp_obj_t colour) { mp_obj_t GenericST7789_update_pen(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
GenericST7789_obj_t *self = MP_OBJ_TO_PTR2(self_in, GenericST7789_obj_t); enum { ARG_self, ARG_i, ARG_r, ARG_g, ARG_b };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_i, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_r, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_g, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_b, MP_ARG_REQUIRED | MP_ARG_INT }
};
self->st7789->set_palette( mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_obj_get_int(index) & 0xff, mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
mp_obj_get_int(colour) & 0xffff
GenericST7789_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, GenericST7789_obj_t);
self->st7789->update_pen(
args[ARG_i].u_int & 0xff,
args[ARG_r].u_int & 0xff,
args[ARG_g].u_int & 0xff,
args[ARG_b].u_int & 0xff
); );
return mp_const_none; return mp_const_none;
} }
mp_obj_t GenericST7789_reserve_palette(mp_obj_t self_in) {
GenericST7789_obj_t *self = MP_OBJ_TO_PTR2(self_in, GenericST7789_obj_t);
int result = self->st7789->reserve_palette();
if (result == -1) mp_raise_ValueError("reserve_palette failed. No space in palette!");
return mp_obj_new_int(result);
}
mp_obj_t GenericST7789_create_pen(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { mp_obj_t GenericST7789_create_pen(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_r, ARG_g, ARG_b }; enum { ARG_self, ARG_r, ARG_g, ARG_b };
static const mp_arg_t allowed_args[] = { static const mp_arg_t allowed_args[] = {

View File

@ -27,9 +27,8 @@ extern mp_obj_t GenericST7789_update(mp_obj_t self_in);
extern mp_obj_t GenericST7789_set_backlight(mp_obj_t self_in, mp_obj_t brightness); extern mp_obj_t GenericST7789_set_backlight(mp_obj_t self_in, mp_obj_t brightness);
// Palette management // Palette management
extern mp_obj_t GenericST7789_set_palette_mode(mp_obj_t self_in, mp_obj_t mode); extern mp_obj_t GenericST7789_update_pen(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t GenericST7789_set_palette(mp_obj_t self_in, mp_obj_t index, mp_obj_t colour); extern mp_obj_t GenericST7789_reset_pen(mp_obj_t self_in, mp_obj_t pen);
extern mp_obj_t GenericST7789_reserve_palette(mp_obj_t self_in);
// Pen // Pen
extern mp_obj_t GenericST7789_set_pen(mp_obj_t self_in, mp_obj_t pen); extern mp_obj_t GenericST7789_set_pen(mp_obj_t self_in, mp_obj_t pen);