mirror of
https://github.com/Klipper3d/klipper.git
synced 2025-12-24 00:28:34 -07:00
Convert to palette
This commit is contained in:
parent
780fb7feea
commit
e1101e6672
3 changed files with 150 additions and 63 deletions
|
|
@ -16,13 +16,6 @@ GC9A01_CMD_DELAY = .000020 # 20us between commands
|
|||
DISPLAY_WIDTH = 240
|
||||
DISPLAY_HEIGHT = 240
|
||||
|
||||
# RGB565 color definitions
|
||||
COLOR_BLACK = 0x0000
|
||||
COLOR_WHITE = 0xFFFF
|
||||
COLOR_RED = 0xF800
|
||||
COLOR_GREEN = 0x07E0
|
||||
COLOR_BLUE = 0x001F
|
||||
|
||||
class GC9A01:
|
||||
def __init__(self, config):
|
||||
self.printer = config.get_printer()
|
||||
|
|
@ -63,14 +56,14 @@ class GC9A01:
|
|||
self.width = base_width
|
||||
self.height = base_height
|
||||
|
||||
# Display colors
|
||||
self.fg_color = config.getint('fg_color', COLOR_WHITE,
|
||||
minval=0, maxval=65535)
|
||||
self.bg_color = config.getint('bg_color', COLOR_BLACK,
|
||||
minval=0, maxval=65535)
|
||||
# Display colors (palette indices 0-15)
|
||||
self.fg_color = config.getint('fg_color', 15,
|
||||
minval=0, maxval=15)
|
||||
self.bg_color = config.getint('bg_color', 0,
|
||||
minval=0, maxval=15)
|
||||
|
||||
# Framebuffer for RGB565 format (2 bytes per pixel)
|
||||
fb_size = self.width * self.height * 2
|
||||
# Framebuffer for 4-bit color (2 pixels per byte)
|
||||
fb_size = (self.width * self.height + 1) // 2
|
||||
self.framebuffer = bytearray(fb_size)
|
||||
self.glyphs = {}
|
||||
|
||||
|
|
@ -92,6 +85,7 @@ class GC9A01:
|
|||
self.start_write_cmd = None
|
||||
self.send_data_cmd = None
|
||||
self.fill_rect_cmd = None
|
||||
self.set_palette_cmd = None
|
||||
|
||||
# Register with printer
|
||||
self.printer.register_event_handler("klippy:ready", self._handle_ready)
|
||||
|
|
@ -123,8 +117,12 @@ class GC9A01:
|
|||
self.send_data_cmd = self.mcu.lookup_command(
|
||||
"gc9a01_send_data oid=%c data=%*s", cq=cmd_queue)
|
||||
self.fill_rect_cmd = self.mcu.lookup_command(
|
||||
"gc9a01_fill_rect oid=%c x0=%hu y0=%hu x1=%hu y1=%hu color=%hu",
|
||||
"gc9a01_fill_rect oid=%c x0=%hu y0=%hu x1=%hu y1=%hu color=%c",
|
||||
cq=cmd_queue)
|
||||
self.set_palette_cmd = self.mcu.lookup_command(
|
||||
"gc9a01_set_palette oid=%c c1=%hu c2=%hu c3=%hu c4=%hu "
|
||||
"c5=%hu c6=%hu c7=%hu c8=%hu c9=%hu c10=%hu c11=%hu "
|
||||
"c12=%hu c13=%hu c14=%hu", cq=cmd_queue)
|
||||
|
||||
logging.debug("GC9A01 MCU commands configured")
|
||||
except Exception as e:
|
||||
|
|
@ -156,30 +154,42 @@ class GC9A01:
|
|||
self.set_window_cmd.send([self.oid, x0, y0, x1, y1],
|
||||
reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
|
||||
def fill_rectangle(self, x0, y0, x1, y1, color):
|
||||
"""Fill a rectangle on the display with a color"""
|
||||
def fill_rectangle(self, x0, y0, x1, y1, color_idx):
|
||||
"""Fill a rectangle on the display with a palette color index"""
|
||||
if self.fill_rect_cmd is None:
|
||||
return
|
||||
logging.debug("GC9A01 fill_rect %d,%d to %d,%d color=0x%04X",
|
||||
x0, y0, x1, y1, color)
|
||||
self.fill_rect_cmd.send([self.oid, x0, y0, x1, y1, color],
|
||||
color_idx &= 0x0F
|
||||
logging.debug("GC9A01 fill_rect %d,%d to %d,%d color_idx=%d",
|
||||
x0, y0, x1, y1, color_idx)
|
||||
self.fill_rect_cmd.send([self.oid, x0, y0, x1, y1, color_idx],
|
||||
reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
self.window_x0 = x0
|
||||
self.window_y0 = y0
|
||||
self.window_x1 = x1
|
||||
self.window_y1 = y1
|
||||
|
||||
def set_palette(self, colors):
|
||||
"""Upload custom palette colors (indices 1-14)
|
||||
colors: list of 14 RGB565 values
|
||||
"""
|
||||
if self.set_palette_cmd is None:
|
||||
return
|
||||
if len(colors) != 14:
|
||||
logging.error("GC9A01 set_palette: need exactly 14 colors")
|
||||
return
|
||||
self.set_palette_cmd.send([self.oid] + colors,
|
||||
reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
|
||||
def init(self):
|
||||
"""Initialize the display"""
|
||||
self.clear()
|
||||
logging.debug("GC9A01 init called")
|
||||
|
||||
def clear(self):
|
||||
"""Clear the framebuffer (fill with background color)"""
|
||||
color_bytes = bytes([(self.bg_color >> 8) & 0xFF,
|
||||
self.bg_color & 0xFF])
|
||||
for i in range(0, len(self.framebuffer), 2):
|
||||
self.framebuffer[i:i+2] = color_bytes
|
||||
"""Clear the framebuffer (fill with background color index)"""
|
||||
fill_byte = (self.bg_color << 4) | self.bg_color
|
||||
for i in range(len(self.framebuffer)):
|
||||
self.framebuffer[i] = fill_byte
|
||||
self.dirty_x0 = self.width
|
||||
self.dirty_y0 = self.height
|
||||
self.dirty_x1 = -1
|
||||
|
|
@ -195,10 +205,10 @@ class GC9A01:
|
|||
logging.debug("GC9A01 flush: no dirty regions, skipping")
|
||||
return
|
||||
|
||||
# Send only the dirty region
|
||||
x0 = self.dirty_x0
|
||||
# Align dirty region to even pixel boundaries (2 pixels per byte)
|
||||
x0 = self.dirty_x0 & ~1 # Round down to even
|
||||
y0 = self.dirty_y0
|
||||
x1 = self.dirty_x1
|
||||
x1 = self.dirty_x1 | 1 # Round up to odd
|
||||
y1 = self.dirty_y1
|
||||
|
||||
self.set_window(x0, y0, x1, y1)
|
||||
|
|
@ -208,15 +218,18 @@ class GC9A01:
|
|||
reqclock=BACKGROUND_PRIORITY_CLOCK)
|
||||
|
||||
# Send framebuffer data for dirty region
|
||||
max_chunk = 32
|
||||
bytes_per_pixel = 2
|
||||
row_width = self.width * bytes_per_pixel
|
||||
dirty_width = (x1 - x0 + 1) * bytes_per_pixel
|
||||
max_chunk = 32 # Max bytes per send
|
||||
dirty_width = x1 - x0 + 1
|
||||
|
||||
for y in range(y0, y1 + 1):
|
||||
row_start = y * row_width + x0 * bytes_per_pixel
|
||||
row_end = row_start + dirty_width
|
||||
row_data = self.framebuffer[row_start:row_end]
|
||||
# Calculate byte positions for this row
|
||||
row_start_pixel = y * self.width + x0
|
||||
row_end_pixel = row_start_pixel + dirty_width
|
||||
|
||||
byte_start = row_start_pixel // 2
|
||||
byte_end = (row_end_pixel + 1) // 2
|
||||
|
||||
row_data = self.framebuffer[byte_start:byte_end]
|
||||
|
||||
# Send row in chunks
|
||||
for i in range(0, len(row_data), max_chunk):
|
||||
|
|
@ -230,22 +243,26 @@ class GC9A01:
|
|||
self.dirty_x1 = -1
|
||||
self.dirty_y1 = -1
|
||||
|
||||
def draw_pixel(self, x, y, color):
|
||||
"""Draw a single pixel (RGB565 format)"""
|
||||
def draw_pixel(self, x, y, color_idx):
|
||||
"""Draw a single pixel using 4-bit color index (0-15)"""
|
||||
if x >= self.width or y >= self.height or x < 0 or y < 0:
|
||||
return
|
||||
|
||||
pos = (y * self.width + x) * 2
|
||||
color_high = (color >> 8) & 0xFF
|
||||
color_low = color & 0xFF
|
||||
color_idx &= 0x0F
|
||||
pixel_num = y * self.width + x
|
||||
byte_pos = pixel_num // 2
|
||||
is_high = (pixel_num % 2) == 0
|
||||
|
||||
# Only update if color changed
|
||||
if (self.framebuffer[pos] == color_high
|
||||
and self.framebuffer[pos + 1] == color_low):
|
||||
old_byte = self.framebuffer[byte_pos]
|
||||
if is_high:
|
||||
new_byte = (color_idx << 4) | (old_byte & 0x0F)
|
||||
else:
|
||||
new_byte = (old_byte & 0xF0) | color_idx
|
||||
|
||||
if old_byte == new_byte:
|
||||
return
|
||||
|
||||
self.framebuffer[pos] = color_high
|
||||
self.framebuffer[pos + 1] = color_low
|
||||
self.framebuffer[byte_pos] = new_byte
|
||||
|
||||
# Update dirty region
|
||||
self.dirty_x0 = min(self.dirty_x0, x)
|
||||
|
|
@ -253,9 +270,9 @@ class GC9A01:
|
|||
self.dirty_x1 = max(self.dirty_x1, x)
|
||||
self.dirty_y1 = max(self.dirty_y1, y)
|
||||
|
||||
def fill_rect(self, x, y, w, h, color):
|
||||
"""Fill a rectangle with a color"""
|
||||
self.fill_rectangle(x, y, x + w - 1, y + h - 1, color)
|
||||
def fill_rect(self, x, y, w, h, color_idx):
|
||||
"""Fill a rectangle with a palette color index"""
|
||||
self.fill_rectangle(x, y, x + w - 1, y + h - 1, color_idx)
|
||||
|
||||
# Display interface compatibility methods
|
||||
# For testing purposes only
|
||||
|
|
@ -321,8 +338,9 @@ class GC9A01:
|
|||
for col in range(width):
|
||||
pixel_x = char_x + col
|
||||
pixel_y = y * height + row + centered_y
|
||||
color = self.fg_color if (bits & (0x8000 >> col)) else self.bg_color
|
||||
self.draw_pixel(pixel_x, pixel_y, color)
|
||||
color_idx = (self.fg_color if (bits & (0x8000 >> col))
|
||||
else self.bg_color)
|
||||
self.draw_pixel(pixel_x, pixel_y, color_idx)
|
||||
return 2
|
||||
|
||||
# Handle icon16x16 format
|
||||
|
|
@ -344,12 +362,14 @@ class GC9A01:
|
|||
for col in range(width):
|
||||
pixel_x = char_x + col
|
||||
pixel_y = y * height + row + centered_y
|
||||
color = self.fg_color if (byte1 & (0x80 >> col)) else self.bg_color
|
||||
self.draw_pixel(pixel_x, pixel_y, color)
|
||||
color_idx = (self.fg_color if (byte1 & (0x80 >> col))
|
||||
else self.bg_color)
|
||||
self.draw_pixel(pixel_x, pixel_y, color_idx)
|
||||
|
||||
pixel_x = char_x + col + width
|
||||
color = self.fg_color if (byte2 & (0x80 >> col)) else self.bg_color
|
||||
self.draw_pixel(pixel_x, pixel_y, color)
|
||||
color_idx = (self.fg_color if (byte2 & (0x80 >> col))
|
||||
else self.bg_color)
|
||||
self.draw_pixel(pixel_x, pixel_y, color_idx)
|
||||
return 2
|
||||
|
||||
return 0
|
||||
|
|
@ -360,4 +380,4 @@ class GC9A01:
|
|||
|
||||
def load_config(config):
|
||||
"""Klipper config loader - this is the entry point"""
|
||||
return GC9A01(config)
|
||||
return GC9A01(config)
|
||||
|
|
|
|||
|
|
@ -228,6 +228,9 @@ config WANT_ST7920
|
|||
config WANT_HD44780
|
||||
bool "Support HD44780 LCD display"
|
||||
depends on HAVE_GPIO
|
||||
config WANT_GC9A01
|
||||
bool "Support GC9A01 LCD display"
|
||||
depends on WANT_SPI
|
||||
comment "Accelerometer chips"
|
||||
depends on WANT_SPI || WANT_I2C
|
||||
config WANT_ADXL345
|
||||
|
|
|
|||
|
|
@ -72,6 +72,24 @@ struct gc9a01 {
|
|||
#define GC9A01_IREG_0x8E 0x8E
|
||||
#define GC9A01_IREG_0x8F 0x8F
|
||||
|
||||
static uint16_t palette[16] = {
|
||||
0x0000, // 0: RGB( 0, 0, 0) #000000 Black
|
||||
0xF800, // 1: RGB(255, 0, 0) #FF0000 Red
|
||||
0x07E0, // 2: RGB( 0,255, 0) #00FF00 Green
|
||||
0xFFE0, // 3: RGB(255,255, 0) #FFFF00 Yellow
|
||||
0x001F, // 4: RGB( 0, 0,255) #0000FF Blue
|
||||
0xF81F, // 5: RGB(255, 0,255) #FF00FF Magenta
|
||||
0x07FF, // 6: RGB( 0,255,255) #00FFFF Cyan
|
||||
0xFFFF, // 7: RGB(255,255,255) #FFFFFF White
|
||||
0x8410, // 8: RGB(132,130,132) #848284 Dark Gray
|
||||
0x4208, // 9: RGB( 66, 65, 66) #424142 Very Dark Gray
|
||||
0xA514, // 10: RGB(165,162,165) #A5A2A5 Medium Gray
|
||||
0x6318, // 11: RGB( 99, 97,198) #6361C6 Blue-Gray
|
||||
0xC618, // 12: RGB(198,195,198) #C6C3C6 Light Gray
|
||||
0x39E7, // 13: RGB( 57, 60, 57) #393C39 Dark Green-Gray
|
||||
0xE73C, // 14: RGB(231,231,231) #E7E7E7 Very Light Gray
|
||||
0xFFFF // 15: RGB(255,255,255) #FFFFFF White
|
||||
};
|
||||
|
||||
/****************************************************************
|
||||
* Timing functions
|
||||
|
|
@ -207,13 +225,13 @@ command_config_gc9a01(uint32_t *args)
|
|||
gc9a01_cmd_with_data(g, GC9A01_COLMOD, 1, colmod_data);
|
||||
|
||||
// Memory Data Access Control - apply rotation
|
||||
uint8_t madctl_value = 0x00;
|
||||
uint8_t madctl_value = 0x08; // BGR bit
|
||||
if (g->rotation == 90)
|
||||
madctl_value = 0x60; // MV=1, MX=1
|
||||
madctl_value |= 0x60;
|
||||
else if (g->rotation == 180)
|
||||
madctl_value = 0xC0; // MY=1, MX=1
|
||||
madctl_value |= 0xC0;
|
||||
else if (g->rotation == 270)
|
||||
madctl_value = 0xA0; // MV=1, MY=1
|
||||
madctl_value |= 0xA0;
|
||||
uint8_t madctl_data[] = {madctl_value};
|
||||
gc9a01_cmd_with_data(g, GC9A01_MADCTL, 1, madctl_data);
|
||||
|
||||
|
|
@ -325,7 +343,8 @@ command_gc9a01_fill_rect(uint32_t *args)
|
|||
uint16_t y0 = args[2];
|
||||
uint16_t x1 = args[3];
|
||||
uint16_t y1 = args[4];
|
||||
uint16_t color = args[5];
|
||||
uint8_t color_idx = args[5] & 0x0F;
|
||||
uint16_t color = palette[color_idx];
|
||||
uint32_t pixels = (x1 - x0 + 1) * (y1 - y0 + 1) * 2;
|
||||
uint8_t color_high = (color >> 8) & 0xFF;
|
||||
uint8_t color_low = color & 0xFF;
|
||||
|
|
@ -361,15 +380,36 @@ command_gc9a01_fill_rect(uint32_t *args)
|
|||
}
|
||||
}
|
||||
DECL_COMMAND(command_gc9a01_fill_rect,
|
||||
"gc9a01_fill_rect oid=%c x0=%hu y0=%hu x1=%hu y1=%hu color=%hu");
|
||||
"gc9a01_fill_rect oid=%c x0=%hu y0=%hu x1=%hu y1=%hu color=%c");
|
||||
|
||||
void
|
||||
command_gc9a01_send_data(uint32_t *args)
|
||||
{
|
||||
struct gc9a01 *g = oid_lookup(args[0], command_config_gc9a01);
|
||||
uint8_t len = args[1], *data = command_decode_ptr(args[2]);
|
||||
if (len > 0)
|
||||
gc9a01_send_data(g, len, data);
|
||||
uint8_t out_buf[GC9A01_LINE_SIZE];
|
||||
uint16_t out_idx = 0;
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
uint8_t byte = data[i];
|
||||
uint8_t idx1 = (byte >> 4) & 0x0F;
|
||||
uint8_t idx2 = byte & 0x0F;
|
||||
uint16_t color1 = palette[idx1];
|
||||
uint16_t color2 = palette[idx2];
|
||||
|
||||
out_buf[out_idx++] = (color1 >> 8) & 0xFF;
|
||||
out_buf[out_idx++] = color1 & 0xFF;
|
||||
out_buf[out_idx++] = (color2 >> 8) & 0xFF;
|
||||
out_buf[out_idx++] = color2 & 0xFF;
|
||||
|
||||
if (out_idx >= GC9A01_LINE_SIZE) {
|
||||
gc9a01_send_data(g, out_idx, out_buf);
|
||||
out_idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (out_idx > 0)
|
||||
gc9a01_send_data(g, out_idx, out_buf);
|
||||
}
|
||||
DECL_COMMAND(command_gc9a01_send_data, "gc9a01_send_data oid=%c data=%*s");
|
||||
|
||||
|
|
@ -407,6 +447,30 @@ command_gc9a01_set_window(uint32_t *args)
|
|||
DECL_COMMAND(command_gc9a01_set_window,
|
||||
"gc9a01_set_window oid=%c x0=%hu y0=%hu x1=%hu y1=%hu");
|
||||
|
||||
void
|
||||
command_gc9a01_set_palette(uint32_t *args)
|
||||
{
|
||||
oid_lookup(args[0], command_config_gc9a01);
|
||||
palette[1] = args[1];
|
||||
palette[2] = args[2];
|
||||
palette[3] = args[3];
|
||||
palette[4] = args[4];
|
||||
palette[5] = args[5];
|
||||
palette[6] = args[6];
|
||||
palette[7] = args[7];
|
||||
palette[8] = args[8];
|
||||
palette[9] = args[9];
|
||||
palette[10] = args[10];
|
||||
palette[11] = args[11];
|
||||
palette[12] = args[12];
|
||||
palette[13] = args[13];
|
||||
palette[14] = args[14];
|
||||
}
|
||||
DECL_COMMAND(command_gc9a01_set_palette,
|
||||
"gc9a01_set_palette oid=%c c1=%hu c2=%hu c3=%hu c4=%hu "
|
||||
"c5=%hu c6=%hu c7=%hu c8=%hu c9=%hu c10=%hu c11=%hu "
|
||||
"c12=%hu c13=%hu c14=%hu");
|
||||
|
||||
void
|
||||
gc9a01_shutdown(void)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue