Initial code

pull/20032/head
elpekenin 2023-03-06 11:07:14 +01:00
parent b8c9cb8b09
commit d53c3a8816
2 changed files with 301 additions and 0 deletions

View File

@ -0,0 +1,215 @@
// Copyright 2023 Pablo Martinez (@elpekenin)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "qp_comms.h"
#include "qp_draw.h"
#include "qp_eink_panel.h"
#include "qp_internal.h"
#include "qp_surface.h"
#include "qp_surface_internal.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// NOTE: The way in which data is stored may break if QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE is not an even number of bytes
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper functions
// Reset can_flush flag back to true after timeout
uint32_t can_flush_callback(uint32_t trigger_time, void *cb_arg) {
eink_panel_dc_reset_painter_device_t *driver = (eink_panel_dc_reset_painter_device_t *)cb_arg;
driver->can_flush = true;
return 0;
}
// Set can_flush to false and schedule its cleanup
void qp_eink_update_can_flush(painter_device_t device) {
eink_panel_dc_reset_painter_device_t *driver = (eink_panel_dc_reset_painter_device_t *)device;
driver->can_flush = false;
defer_exec(driver->timeout, can_flush_callback, (void *)device);
}
static inline void decode_masked_pixels(uint8_t *pixels, uint32_t byte, uint8_t *black, uint8_t *color) {
/* Convert pixel data into convenient representation, buffer holds data as:
* B0C0 B1C1 B2C2 B3C3 || B4C4 B5C5 B6C6 B7C7 ...
*
* This function shuffles it so we get
* black_data: B7B6B5B4B3B2B1B0
* color_data: C7C6C5C4C3C2C1C0
*/
uint16_t raw_data = (pixels[byte] << 8) | (pixels[byte + 1]);
// clear data so we can simply |=
*black = 0;
*color = 0;
uint16_t black_mask = 1 << 15;
uint16_t color_mask = 1 << 14;
for (uint8_t i = 0; i < 8; ++i) {
bool black_bit = raw_data & black_mask;
bool color_bit = raw_data & color_mask;
black_mask >>= 2;
color_mask >>= 2;
*black |= black_bit << i;
*color |= color_bit << i;
}
}
// Interpolate any HSV888 to the nearest of the three
static inline uint32_t hsv_distance(HSV hsv1, HSV hsv2) {
return (hsv1.h - hsv2.h) * (hsv1.h - hsv2.h) + (hsv1.s - hsv2.s) * (hsv1.s - hsv2.s) + (hsv1.v - hsv2.v) * (hsv1.v - hsv2.v);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter API implementations
// Power control
bool qp_eink_panel_power(painter_device_t device, bool power_on) {
painter_driver_t * driver = (painter_driver_t *)device;
eink_panel_dc_reset_painter_driver_vtable_t *vtable = (eink_panel_dc_reset_painter_driver_vtable_t *)driver->driver_vtable;
qp_comms_command(device, power_on ? vtable->opcodes.display_on : vtable->opcodes.display_off);
return true;
}
// Screen clear
bool qp_eink_panel_clear(painter_device_t device) {
painter_driver_t *driver = (painter_driver_t *)device;
qp_init(device, driver->rotation);
return true;
}
// Screen flush
bool qp_eink_panel_flush(painter_device_t device) {
eink_panel_dc_reset_painter_device_t * driver = (eink_panel_dc_reset_painter_device_t *)device;
eink_panel_dc_reset_painter_driver_vtable_t *vtable = (eink_panel_dc_reset_painter_driver_vtable_t *)driver->base.driver_vtable;
surface_painter_device_t * black = (surface_painter_device_t *)driver->black_surface;
surface_painter_device_t * color = (surface_painter_device_t *)driver->color_surface;
uint32_t n_bytes = SURFACE_REQUIRED_BUFFER_BYTE_SIZE(driver->base.panel_width, driver->base.panel_height, 1);
if (!black->dirty.is_dirty && !color->dirty.is_dirty) {
qp_dprintf("qp_eink_panel_flush: done (no changes to be sent)\n");
return true;
}
if (!driver->can_flush) {
qp_dprintf("qp_eink_panel_flush: fail (can_flush == false)\n");
return false;
}
qp_comms_command(device, vtable->opcodes.send_black_data);
qp_comms_send(device, black->buffer, n_bytes);
qp_comms_command(device, vtable->opcodes.send_color_data);
qp_comms_send(device, color->buffer, n_bytes);
qp_comms_command(device, vtable->opcodes.refresh);
qp_eink_update_can_flush(device);
return true;
}
// Viewport to draw to
bool qp_eink_panel_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
eink_panel_dc_reset_painter_device_t *driver = (eink_panel_dc_reset_painter_device_t *)device;
qp_viewport(driver->black_surface, left, top, right, bottom);
qp_viewport(driver->color_surface, left, top, right, bottom);
return true;
}
// Stream pixel data to the current write position
bool qp_eink_panel_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) {
eink_panel_dc_reset_painter_device_t *driver = (eink_panel_dc_reset_painter_device_t *)device;
uint8_t * pixels = (uint8_t *)pixel_data;
uint32_t i = 0;
uint8_t black_data, color_data;
while (i < native_pixel_count) {
// at most, 8 pixels per cycle
uint8_t pixels_this_loop = QP_MIN(native_pixel_count - i, 8);
uint32_t byte = i / 4;
// stream data to display
decode_masked_pixels(pixels, byte, &black_data, &color_data);
// this is very slow with debugging on, call the vtable's function manually instead
qp_pixdata(driver->black_surface, (const void *)&black_data, pixels_this_loop);
qp_pixdata(driver->color_surface, (const void *)&color_data, pixels_this_loop);
// update position
i += pixels_this_loop;
}
return true;
}
// Convert supplied palette entries into their native equivalents
bool qp_eink_panel_palette_convert(painter_device_t device, int16_t palette_size, qp_pixel_t *palette) {
eink_panel_dc_reset_painter_device_t *driver = (eink_panel_dc_reset_painter_device_t *)device;
for (int16_t i = 0; i < palette_size; ++i) {
HSV hsv = (HSV){palette[i].hsv888.h, palette[i].hsv888.s, palette[i].hsv888.v};
uint32_t white_distance = hsv_distance(hsv, (HSV){ HSV_WHITE });
uint32_t black_distance = hsv_distance(hsv, (HSV){ HSV_BLACK });
uint32_t color_distance = hsv_distance(hsv, driver->color);
// Default to white
bool black = false;
bool color = false;
uint32_t min_distance = QP_MIN(white_distance, QP_MIN(black_distance, color_distance));
if (min_distance == black_distance)
black = true;
else if (min_distance == color_distance)
color = true;
uint8_t converted = (black << 1) | (color << 0);
palette[i].mono = converted ^ driver->invert_mask;
}
return true;
}
// Append pixels to the target location, keyed by the pixel index
bool qp_eink_panel_append_pixels(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices) {
for (uint32_t i = 0; i < pixel_count; ++i) {
uint32_t pixel_num = pixel_offset + i;
// each pixel takes 2 bits, aka each byte holds 4 pixels, offset based on that
uint32_t byte_offset = pixel_num / 4;
uint8_t bit_offset = 3 - (pixel_num % 4);
// check each color bit from palette
bool black_bit = palette[palette_indices[i]].mono & 0b10;
bool color_bit = palette[palette_indices[i]].mono & 0b01;
// compute where data goes
// Data ends up arranged: B0R0B1R1B2R2B3R3 | ...
uint8_t black_mask = 1 << (2 * bit_offset + 1);
uint8_t color_mask = 1 << (2 * bit_offset + 0);
// add it
if (black_bit)
target_buffer[byte_offset] |= black_mask;
else
target_buffer[byte_offset] &= ~black_mask;
if (color_bit)
target_buffer[byte_offset] |= color_mask;
else
target_buffer[byte_offset] &= ~color_mask;
}
return true;
}
bool qp_eink_panel_append_pixdata(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte) {
qp_dprintf("Shouldn't call append_pixdata on e-Ink displays\n");
return false;
}

View File

@ -0,0 +1,86 @@
// Copyright 2023 Pablo Martinez (@elpekenin)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "color.h"
#include "qp_internal.h"
#include "qp_surface.h"
#ifdef QUANTUM_PAINTER_SPI_ENABLE
# include "qp_comms_spi.h"
#endif // QUANTUM_PAINTER_SPI_ENABLE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helper to create buffers with the apropiate size
#define EINK_BYTES_REQD(w, h) (SURFACE_REQUIRED_BUFFER_BYTE_SIZE(w, h, 2))
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common TFT panel implementation using D/C, and RST pins.
// Driver vtable with extras
typedef struct eink_panel_dc_reset_painter_driver_vtable_t {
painter_driver_vtable_t base; // must be first, so it can be cast to/from the painter_driver_vtable_t* type
// Whether or not the x/y coords should be swapped on 90/270 rotation
bool swap_window_coords;
// Opcodes for normal display operation
// some may not exist on some displays
struct {
uint8_t display_on;
uint8_t display_off;
uint8_t send_black_data;
uint8_t send_color_data;
uint8_t refresh;
} opcodes;
} eink_panel_dc_reset_painter_driver_vtable_t;
// Device definition
typedef struct eink_panel_dc_reset_painter_device_t {
painter_driver_t base; // must be first, so it can be cast to/from the painter_device_t* type
// have to wait between flushes to avoid damaging the screen, time in ms
uint32_t timeout;
bool can_flush;
/** Information about the pixel format, default values (non-inverted) are
*
* Black bit: 0 for white / 1 for black
* Color bit: 0 for white or black / 1 for third color
*
* Represented as a bitmask where a 1 means "needs inversion"
* 0000 00BC
*/
HSV color;
uint8_t invert_mask;
// Storage for each color's data
painter_device_t black_surface;
painter_device_t color_surface;
union {
#ifdef QUANTUM_PAINTER_SPI_ENABLE
// SPI-based configurables
qp_comms_spi_dc_reset_config_t spi_dc_reset_config;
#endif // QUANTUM_PAINTER_SPI_ENABLE
// TODO: I2C/parallel etc.
};
} eink_panel_dc_reset_painter_device_t;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Forward declarations for injecting into concrete driver vtables
bool qp_eink_panel_power(painter_device_t device, bool power_on);
bool qp_eink_panel_clear(painter_device_t device);
void qp_eink_update_can_flush(painter_device_t device);
bool qp_eink_panel_flush(painter_device_t device);
bool qp_eink_panel_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom);
bool qp_eink_panel_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count);
bool qp_eink_panel_palette_convert(painter_device_t device, int16_t palette_size, qp_pixel_t *palette);
bool qp_eink_panel_append_pixels(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices);
bool qp_eink_panel_append_pixdata(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte);