diff --git a/5in65_acep_7c_display_driver.c b/5in65_acep_7c_display_driver.c deleted file mode 100644 index 91483a1..0000000 --- a/5in65_acep_7c_display_driver.c +++ /dev/null @@ -1,732 +0,0 @@ -/* - * This file is part of AtomGL. - * - * Copyright 2022 Davide Bettio - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#define DISPLAY_WIDTH 600 -#define DISPLAY_HEIGHT 448 - -#include "display_items.h" -#include "display_common.h" -#include "draw_common.h" -#include "font.c" -#include "image_helpers.h" -#include "spi_display.h" - -#define CHAR_WIDTH 8 - -#define REPORT_UNEXPECTED_MSGS 0 -#define CHECK_OVERFLOW 1 -#define SELF_TEST 0 - -static const char *TAG = "5in65_acep_7c_display_driver"; - -static void send_message(term pid, term message, GlobalContext *global); -static void clear_screen(Context *ctx, int color); - -struct SPI -{ - struct SPIDisplay spi_disp; - - int busy_gpio; - int dc_gpio; - int reset_gpio; - - Context *ctx; - - int count_to_refresh; - uint64_t last_refresh; -}; - -struct PendingReply -{ - uint64_t pending_call_ref_ticks; - term pending_call_pid; -}; - -static QueueHandle_t display_messages_queue; - -static inline float square(float p) -{ - return p * p; -} - -static uint8_t dither_acep7(int x, int y, uint8_t r, uint8_t g, uint8_t b) -{ - const uint8_t m[4][4] = { - { 0, 8, 2, 10 }, - { 12, 4, 14, 6 }, - { 3, 11, 1, 9 }, - { 15, 7, 13, 5 } - }; - - // following r parameters have been found using standard deviation - // that gives a decent result - int r1 = r + roundf(92.0 * ((float) m[x % 4][y % 4] * 0.0625 - 0.5)); - int g1 = g + roundf(85.0 * ((float) m[x % 4][y % 4] * 0.0625 - 0.5)); - int b1 = b + roundf(65.0 * ((float) m[x % 4][y % 4] * 0.0625 - 0.5)); - - // values found by trial and error - // they try to get closer to real colors than pure saturated RGB colors - uint8_t colors[7][3] = { - { 0x00, 0x00, 0x00 }, - { 0xFF, 0xFF, 0xFF }, - { 0x00, 0xFF, 0x00 }, - { 0x00, 0x00, 0xFF }, - { 0xFF, 0x00, 0x00 }, - { 0xFF, 0xFF, 0x00 }, - { 0xFF, 0x80, 0x00 } - }; - - float min = INT_MAX; - int min_index = 0; - - for (int i = 0; i < 7; i++) { - int r2 = colors[i][0]; - int g2 = colors[i][1]; - int b2 = colors[i][2]; - -#ifdef NO_WEIGHTS - float d = square((r2 - r1)) + square((g2 - g1)) + square((b2 - b1)); -#else - float d = square((r2 - r1) * 0.30) + square((g2 - g1) * 0.59) + square((b2 - b1) * 0.11); -#endif - - if (d < min) { - min = d; - min_index = i; - } - } - - return min_index; -} - -static void writecommand(struct SPI *spi, uint8_t cmd) -{ - gpio_set_level(spi->dc_gpio, 0); - spi_display_write(&spi->spi_disp, 8, cmd); -} - -static void writedata(struct SPI *spi, uint8_t data) -{ - gpio_set_level(spi->dc_gpio, 1); - spi_display_write(&spi->spi_disp, 8, data); -} - -static void display_reset(struct SPI *spi) -{ - gpio_set_level(spi->reset_gpio, 0); - vTaskDelay(100); - gpio_set_level(spi->reset_gpio, 1); -} - -static void wait_busy_level(struct SPI *spi, int level) -{ - while (gpio_get_level(spi->busy_gpio) != level) { - vTaskDelay(100); - } -} - -static inline void draw_pixel_x(uint8_t *line_buf, int xpos, uint8_t c) -{ -#if CHECK_OVERFLOW - if (xpos > DISPLAY_WIDTH) { - fprintf(stderr, "buf ovf!\n"); - return; - } -#endif - - if ((xpos & 1) == 0) { - line_buf[xpos / 2] = (line_buf[xpos / 2] & 0xF) | (c << 4); - } else { - line_buf[xpos / 2] = (line_buf[xpos / 2] & 0xF0) | c; - } -} - -static int draw_image_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - - int bgcolor_r; - int bgcolor_g; - int bgcolor_b; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor_r = (item->brcolor >> 24) & 0xFF; - bgcolor_g = (item->brcolor >> 16) & 0xFF; - bgcolor_b = (item->brcolor >> 8) & 0xFF; - visible_bg = true; - } else { - bgcolor_r = 0; - bgcolor_g = 0; - bgcolor_b = 0; - visible_bg = false; - } - - int width = item->width; - const char *data = item->data.image_data.pix; - - int drawn_pixels = 0; - - uint32_t *pixels = ((uint32_t *) data) + (ypos - y) * width + (xpos - x); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - uint32_t img_pixel = READ_32_UNALIGNED(pixels); - if ((*pixels >> 24) & 0xFF) { - uint8_t r = img_pixel >> 24; - uint8_t g = (img_pixel >> 16) & 0xFF; - uint8_t b = (img_pixel >> 8) & 0xFF; - - uint8_t c = dither_acep7(xpos + drawn_pixels, ypos, r, g, b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); - - } else if (visible_bg) { - uint8_t c = dither_acep7(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); - - } else { - return drawn_pixels; - } - drawn_pixels++; - pixels++; - } - - return drawn_pixels; -} - -static int draw_scaled_cropped_img_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - - int bgcolor_r; - int bgcolor_g; - int bgcolor_b; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor_r = (item->brcolor >> 24) & 0xFF; - bgcolor_g = (item->brcolor >> 16) & 0xFF; - bgcolor_b = (item->brcolor >> 8) & 0xFF; - visible_bg = true; - } else { - bgcolor_r = 0; - bgcolor_g = 0; - bgcolor_b = 0; - visible_bg = false; - } - - int width = item->width; - const char *data = item->data.image_data_with_size.pix; - - int drawn_pixels = 0; - - int y_scale = item->y_scale; - int x_scale = item->x_scale; - int img_width = item->data.image_data_with_size.width; - - int source_x = item->source_x; - int source_y = item->source_y; - - uint32_t *pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((xpos - x) / x_scale); - - if (source_x + (width / x_scale) > img_width) { - width = (img_width - source_x) * x_scale; - } - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - uint32_t img_pixel = READ_32_UNALIGNED(pixels); - if ((*pixels >> 24) & 0xFF) { - uint8_t r = img_pixel >> 24; - uint8_t g = (img_pixel >> 16) & 0xFF; - uint8_t b = (img_pixel >> 8) & 0xFF; - - uint8_t c = dither_acep7(xpos + drawn_pixels, ypos, r, g, b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); - - } else if (visible_bg) { - uint8_t c = dither_acep7(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); - - } else { - return drawn_pixels; - } - drawn_pixels++; - pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + (j / x_scale); - } - - return drawn_pixels; -} - -static int draw_rect_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int width = item->width; - - uint8_t r = (item->brcolor >> 24) & 0xFF; - uint8_t g = (item->brcolor >> 16) & 0xFF; - uint8_t b = (item->brcolor >> 8) & 0xFF; - - int drawn_pixels = 0; - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - uint8_t c = dither_acep7(xpos + drawn_pixels, ypos, r, g, b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); - drawn_pixels++; - } - - return drawn_pixels; -} - -static int draw_text_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - bool visible_bg; - - int fgcolor_r = (item->data.text_data.fgcolor >> 24) & 0xFF; - int fgcolor_g = (item->data.text_data.fgcolor >> 16) & 0xFF; - int fgcolor_b = (item->data.text_data.fgcolor >> 8) & 0xFF; - - int bgcolor_r; - int bgcolor_g; - int bgcolor_b; - - if (item->brcolor != 0) { - bgcolor_r = (item->brcolor >> 24) & 0xFF; - bgcolor_g = (item->brcolor >> 16) & 0xFF; - bgcolor_b = (item->brcolor >> 8) & 0xFF; - visible_bg = true; - } else { - bgcolor_r = 0; - bgcolor_g = 0; - bgcolor_b = 0; - visible_bg = false; - } - - char *text = (char *) item->data.text_data.text; - - int width = item->width; - - int drawn_pixels = 0; - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - int char_index = j / CHAR_WIDTH; - char c = text[char_index]; - unsigned const char *glyph = fontdata + ((unsigned char) c) * 16; - - unsigned char row = glyph[ypos - y]; - - bool opaque; - int k = j % CHAR_WIDTH; - if (row & (1 << (7 - k))) { - opaque = true; - } else { - opaque = false; - } - - if (opaque) { - uint8_t c = dither_acep7(xpos + drawn_pixels, ypos, fgcolor_r, fgcolor_g, fgcolor_b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); - - } else if (visible_bg) { - uint8_t c = dither_acep7(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); - - } else { - return drawn_pixels; - } - drawn_pixels++; - } - - return drawn_pixels; -} - -void wait_some_time(Context *ctx) -{ - struct SPI *spi = ctx->platform_data; - - struct timeval tv; - gettimeofday(&tv, NULL); - uint64_t now = tv.tv_sec * 1000LL + (tv.tv_usec / 1000LL); - uint64_t delta = now - spi->last_refresh; - if (delta < 2000) { - // Wait 2 seconds before allowing a new refresh - // this is not on datasheets, but without this the screen will not update. - vTaskDelay((2000 - delta) / portTICK_PERIOD_MS); - } -} - -void update_last_refresh_ts(Context *ctx) -{ - struct SPI *spi = ctx->platform_data; - - struct timeval tv; - gettimeofday(&tv, NULL); - spi->last_refresh = tv.tv_sec * 1000LL + (tv.tv_usec / 1000LL); -} - -void maybe_refresh(Context *ctx) -{ - struct SPI *spi = ctx->platform_data; - - spi->count_to_refresh--; - if (spi->count_to_refresh <= 0) { - // 7 is the special "clear screen color" - clear_screen(ctx, 7); - update_last_refresh_ts(ctx); - spi->count_to_refresh = 5; - } -} - -static void do_update(Context *ctx, term display_list) -{ - maybe_refresh(ctx); - // it looks like we need to wait some time - // let's use 2 seconds - wait_some_time(ctx); - - int proper; - int len = term_list_length(display_list, &proper); - - BaseDisplayItem *items = malloc(sizeof(BaseDisplayItem) * len); - - term t = display_list; - for (int i = 0; i < len; i++) { - init_item(&items[i], term_get_list_head(t), ctx); - t = term_get_list_tail(t); - } - - int screen_width = DISPLAY_WIDTH; - int screen_height = DISPLAY_HEIGHT; - struct SPI *spi = ctx->platform_data; - - struct SPIDisplay *spi_disp = &spi->spi_disp; - spi_device_acquire_bus(spi_disp->handle, portMAX_DELAY); - - // resolution command - writecommand(spi, 0x61); - writedata(spi, 0x02); - writedata(spi, 0x58); - writedata(spi, 0x01); - writedata(spi, 0xC0); - - // update command - writecommand(spi, 0x10); - - gpio_set_level(spi->dc_gpio, 1); - - uint8_t *buf = heap_caps_malloc(DISPLAY_WIDTH / 2, MALLOC_CAP_DMA); - memset(buf, 0x11, DISPLAY_WIDTH / 2); - - bool transaction_in_progress = false; - - for (int ypos = 0; ypos < screen_height; ypos++) { - if (transaction_in_progress) { - spi_transaction_t *trans = NULL; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); - } - - int xpos = 0; - while (xpos < screen_width) { - int drawn_pixels = draw_x(buf, xpos, ypos, items, len); - xpos += drawn_pixels; - } - - spi_display_dmawrite(&spi->spi_disp, DISPLAY_WIDTH / 2, buf); - transaction_in_progress = true; - } - - if (transaction_in_progress) { - spi_transaction_t *trans = NULL; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); - } - - // not sure if we should add 0x11, which is end of data command or not - - // power on command - writecommand(spi, 0x04); - wait_busy_level(spi, 1); - - // refresh command - writecommand(spi, 0x12); - wait_busy_level(spi, 1); - - // power off command - writecommand(spi, 0x02); - spi_device_release_bus(spi_disp->handle); - wait_busy_level(spi, 0); - - destroy_items(items, len); - - update_last_refresh_ts(ctx); -} - -static void process_message(Message *message, Context *ctx) -{ - GenMessage gen_message; - if (UNLIKELY(port_parse_gen_message(message->message, &gen_message) != GenCallMessage)) { - fprintf(stderr, "Received invalid message."); - AVM_ABORT(); - } - - term req = gen_message.req; - if (UNLIKELY(!term_is_tuple(req) || term_get_tuple_arity(req) < 1)) { - AVM_ABORT(); - } - term cmd = term_get_tuple_element(req, 0); - - if (cmd == context_make_atom(ctx, "\x6" - "update")) { - - term display_list = term_get_tuple_element(req, 1); - do_update(ctx, display_list); - - } else if (cmd == globalcontext_make_atom(ctx->global, "\xA" "load_image")) { - handle_load_image(req, gen_message.ref, gen_message.pid, ctx); - return; - - } else { -#if REPORT_UNEXPECTED_MSGS - fprintf(stderr, "display: "); - term_display(stderr, req, ctx); - fprintf(stderr, "\n"); -#endif - } - - BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2) + REF_SIZE, heap); - term return_tuple = term_alloc_tuple(2, &heap); - term_put_tuple_element(return_tuple, 0, gen_message.ref); - term_put_tuple_element(return_tuple, 1, OK_ATOM); - - send_message(gen_message.pid, return_tuple, ctx->global); - END_WITH_STACK_HEAP(heap, ctx->global); -} - -static void process_messages(void *arg) -{ - struct SPI *args = arg; - - while (true) { - Message *message; - xQueueReceive(display_messages_queue, &message, portMAX_DELAY); - process_message(message, args->ctx); - - BEGIN_WITH_STACK_HEAP(1, temp_heap); - mailbox_message_dispose(&message->base, &temp_heap); - END_WITH_STACK_HEAP(temp_heap, args->ctx->global); - } -} - -static void send_message(term pid, term message, GlobalContext *global) -{ - int local_process_id = term_to_local_process_id(pid); - globalcontext_send_message(global, local_process_id, message); -} - -static void clear_screen(Context *ctx, int color) -{ - struct SPI *spi = ctx->platform_data; - - uint8_t *buf = heap_caps_malloc(DISPLAY_WIDTH / 2, MALLOC_CAP_DMA); - - struct SPIDisplay *spi_disp = &spi->spi_disp; - spi_device_acquire_bus(spi_disp->handle, portMAX_DELAY); - writecommand(spi, 0x61); - writedata(spi, 0x02); - writedata(spi, 0x58); - writedata(spi, 0x01); - writedata(spi, 0xC0); - writecommand(spi, 0x10); - - gpio_set_level(spi->dc_gpio, 1); - - bool transaction_in_progress = false; - - for (int i = 0; i < DISPLAY_HEIGHT; i++) { - if (transaction_in_progress) { - spi_transaction_t *trans = NULL; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); - } - - // let's ensure a memset otherwise we might generate odd artifacts - memset(buf, color | (color << 4), DISPLAY_WIDTH / 2); - spi_display_dmawrite(spi_disp, DISPLAY_WIDTH / 2, buf); - transaction_in_progress = true; - } - - if (transaction_in_progress) { - spi_transaction_t *trans = NULL; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); - } - - writecommand(spi, 0x04); - wait_busy_level(spi, 1); - writecommand(spi, 0x12); - wait_busy_level(spi, 1); - writecommand(spi, 0x02); - spi_device_release_bus(spi_disp->handle); - wait_busy_level(spi, 0); -} - -static void display_spi_init(Context *ctx, term opts) -{ - struct SPI *spi = malloc(sizeof(struct SPI)); - // TODO check here - - struct SPIDisplayConfig spi_config; - spi_display_init_config(&spi_config); - spi_config.clock_speed_hz = 1000000; - spi_display_parse_config(&spi_config, opts, ctx->global); - spi_display_init(&spi->spi_disp, &spi_config); - - bool ok = display_common_gpio_from_opts(opts, ATOM_STR("\x4", "busy"), &spi->busy_gpio, ctx->global); - ok = ok && display_common_gpio_from_opts(opts, ATOM_STR("\x2", "dc"), &spi->dc_gpio, ctx->global); - ok = ok && display_common_gpio_from_opts(opts, ATOM_STR("\x5", "reset"), &spi->reset_gpio, ctx->global); - if (UNLIKELY(!ok)) { - ESP_LOGE(TAG, "Failed init: invalid display GPIOs."); - return; - } - - gpio_set_direction(spi->reset_gpio, GPIO_MODE_OUTPUT); - gpio_set_level(spi->reset_gpio, 1); - gpio_set_direction(spi->dc_gpio, GPIO_MODE_OUTPUT); - gpio_set_pull_mode(spi->dc_gpio, GPIO_PULLUP_ENABLE); - gpio_set_direction(spi->busy_gpio, GPIO_MODE_INPUT); - gpio_set_pull_mode(spi->busy_gpio, GPIO_PULLUP_ENABLE); - gpio_set_level(spi->dc_gpio, 0); - - esp_err_t ret = spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - ESP_ERROR_CHECK(ret); - display_reset(spi); - - wait_busy_level(spi, 1); - - writecommand(spi, 0x00); - writedata(spi, 0xEF); - writedata(spi, 0x08); - writecommand(spi, 0x01); - writedata(spi, 0x37); - writedata(spi, 0x00); - writedata(spi, 0x23); //datasheet says: 0x05 - writedata(spi, 0x23); //datasheet says: 0x05 - writecommand(spi, 0x03); - writedata(spi, 0x00); - writecommand(spi, 0x06); - writedata(spi, 0xC7); - writedata(spi, 0xC7); - writedata(spi, 0x1D); - writecommand(spi, 0x30); - writedata(spi, 0x3C); - writecommand(spi, 0x40); //datasheet says: 0x41 - writedata(spi, 0x00); - writecommand(spi, 0x50); - writedata(spi, 0x3F); //datasheet says: 0x37 - writecommand(spi, 0x60); - writedata(spi, 0x22); - writecommand(spi, 0x61); - writedata(spi, 0x02); - writedata(spi, 0x58); - writedata(spi, 0x01); - writedata(spi, 0xC0); - writecommand(spi, 0xE3); - writedata(spi, 0xAA); - writecommand(spi, 0x82); - writedata(spi, 0x80); - - vTaskDelay(10); - - writecommand(spi, 0x50); - writedata(spi, 0x37); - spi_device_release_bus(spi->spi_disp.handle); - - ctx->platform_data = spi; - - spi->ctx = ctx; - - update_last_refresh_ts(ctx); - spi->count_to_refresh = 0; - -#if SELF_TEST - for (int i = 0; i < 8; i++) { - fprintf(stderr, "color: %i\n", i); - clear_screen(ctx, i); - vTaskDelay(30000 / portTICK_PERIOD_MS); - } - clear_screen(ctx, 1); - - while (1) - ; -#else - display_messages_queue = xQueueCreate(32, sizeof(Message *)); - xTaskCreate(process_messages, "display", 10000, spi, 1, NULL); -#endif -} - -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx) -{ - MailboxMessage *mbox_msg = mailbox_take_message(&ctx->mailbox); - Message *msg = CONTAINER_OF(mbox_msg, Message, base); - - xQueueSend(display_messages_queue, &msg, 1); - - return NativeContinue; -} - -Context *acep_5in65_7c_display_driver_create_port(GlobalContext *global, term opts) -{ - Context *ctx = context_new(global); - ctx->native_handler = display_driver_consume_mailbox; - display_spi_init(ctx, opts); - return ctx; -} diff --git a/CMakeLists.txt b/CMakeLists.txt index 64bf005..39707ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,15 +27,26 @@ else() endif() idf_component_register(SRCS + "dcs_lcd_commands.c" + "dcs_lcd_display_driver.c" + "dcs_lcd_draw.c" "display_common.c" "display_driver.c" - "5in65_acep_7c_display_driver.c" - "ili934x_display_driver.c" - "ili948x_display_driver.c" + "display_items.c" + "display_message.c" + "display_task.c" + "epaper_color.c" + "epaper_commands.c" + "epaper_draw.c" + "mono_draw.c" + "epaper_display_driver.c" + "font_data.c" "memory_display_driver.c" - "ssd1306_display_driver.c" - "st7789_display_driver.c" + "oled_commands.c" + "oled_display_driver.c" + "spi_dc_driver.c" "spi_display.c" + "ufontlib.c" "backlight_gpio.c" "image_helpers.c" "spng.c" @@ -44,7 +55,7 @@ idf_component_register(SRCS ${OPTIONAL_WHOLE_ARCHIVE} ) -target_compile_definitions(${COMPONENT_TARGET} PRIVATE "-DSPNG_USE_MINIZ=1") +target_compile_definitions(${COMPONENT_TARGET} PRIVATE "-DSPNG_USE_MINIZ=1" "-DENABLE_UFONT") if (IDF_VERSION_MAJOR EQUAL 4) idf_build_set_property( diff --git a/dcs_lcd_color.h b/dcs_lcd_color.h new file mode 100644 index 0000000..cb23e1f --- /dev/null +++ b/dcs_lcd_color.h @@ -0,0 +1,65 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2020-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _DCS_LCD_COLOR_H_ +#define _DCS_LCD_COLOR_H_ + +#include + +#include + +// This functions is taken from: +// https://stackoverflow.com/questions/18937701/combining-two-16-bits-rgb-colors-with-alpha-blending +static inline uint16_t alpha_blend_rgb565(uint32_t fg, uint32_t bg, uint8_t alpha) +{ + alpha = (alpha + 4) >> 3; + bg = (bg | (bg << 16)) & 0b00000111111000001111100000011111; + fg = (fg | (fg << 16)) & 0b00000111111000001111100000011111; + uint32_t result = ((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111; + return (uint16_t) ((result >> 16) | result); +} + +static inline uint8_t rgba8888_get_alpha(uint32_t color) +{ + return color & 0xFF; +} + +static inline uint16_t rgba8888_color_to_rgb565(uint32_t color) +{ + uint8_t r = color >> 24; + uint8_t g = (color >> 16) & 0xFF; + uint8_t b = (color >> 8) & 0xFF; + + return (((uint16_t) (r >> 3)) << 11) | (((uint16_t) (g >> 2)) << 5) | ((uint16_t) b >> 3); +} + +static inline uint16_t rgb565_color_to_surface(uint16_t color16) +{ + return (uint16_t) SPI_SWAP_DATA_TX(color16, 16); +} + +static inline uint16_t uint32_color_to_surface(uint32_t color) +{ + uint16_t color16 = rgba8888_color_to_rgb565(color); + + return rgb565_color_to_surface(color16); +} + +#endif diff --git a/dcs_lcd_commands.c b/dcs_lcd_commands.c new file mode 100644 index 0000000..c4cefef --- /dev/null +++ b/dcs_lcd_commands.c @@ -0,0 +1,406 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2020-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "dcs_lcd_commands.h" + +#include + +#include +#include + +#include +#include + +void dcs_lcd_set_paint_area(struct SPIDCBus *bus, const struct DCSLCDScreen *screen, + int x, int y, int width, int height) +{ + x += screen->x_offset; + y += screen->y_offset; + + spi_dc_write_command(bus, DCS_LCD_CASET); + spi_device_acquire_bus(bus->spi_disp.handle, portMAX_DELAY); + spi_display_write(&bus->spi_disp, 32, (x << 16) | ((x + width) - 1)); + spi_device_release_bus(bus->spi_disp.handle); + + spi_dc_write_command(bus, DCS_LCD_PASET); + spi_device_acquire_bus(bus->spi_disp.handle, portMAX_DELAY); + spi_display_write(&bus->spi_disp, 32, (y << 16) | ((y + height) - 1)); + spi_device_release_bus(bus->spi_disp.handle); +} + +void dcs_lcd_draw_buffer(struct SPIDCBus *bus, const struct DCSLCDScreen *screen, + int pixel_bytes, int x, int y, int width, int height, const void *imgdata) +{ + const uint16_t *data = imgdata; + + dcs_lcd_set_paint_area(bus, screen, x, y, width, height); + + spi_dc_write_command(bus, DCS_LCD_RAMWR); + + int dest_size = width * height; + int chunks = dest_size / 1024; + + spi_device_acquire_bus(bus->spi_disp.handle, portMAX_DELAY); + + if (pixel_bytes == 2) { + int buf_pixel_size = (dest_size > 1024) ? 1024 : dest_size; + uint16_t *tmpbuf = heap_caps_malloc(buf_pixel_size * sizeof(uint16_t), MALLOC_CAP_DMA); + + for (int i = 0; i < chunks; i++) { + const uint16_t *data_b = data + 1024 * i; + for (int j = 0; j < 1024; j++) { + tmpbuf[j] = SPI_SWAP_DATA_TX(data_b[j], 16); + } + spi_display_dma_write(&bus->spi_disp, 1024 * sizeof(uint16_t), tmpbuf); + } + + int last_chunk_size = dest_size - chunks * 1024; + if (last_chunk_size) { + const uint16_t *data_b = data + chunks * 1024; + for (int j = 0; j < last_chunk_size; j++) { + tmpbuf[j] = SPI_SWAP_DATA_TX(data_b[j], 16); + } + spi_display_dma_write(&bus->spi_disp, last_chunk_size * sizeof(uint16_t), tmpbuf); + } + + free(tmpbuf); + + } else { + // ILI9488: RGB565 -> RGB888 (3 bytes/pixel). + const int chunk_pixels = 512; + uint8_t *tmpbuf = heap_caps_malloc(chunk_pixels * 3, MALLOC_CAP_DMA); + + int i = 0; + while (i < dest_size) { + int n = (dest_size - i > chunk_pixels) ? chunk_pixels : (dest_size - i); + + for (int j = 0; j < n; j++) { + uint16_t px = data[i + j]; + uint8_t r5 = (px >> 11) & 0x1F; + uint8_t g6 = (px >> 5) & 0x3F; + uint8_t b5 = (px >> 0) & 0x1F; + + uint8_t r8 = (r5 << 3) | (r5 >> 2); + uint8_t g8 = (g6 << 2) | (g6 >> 4); + uint8_t b8 = (b5 << 3) | (b5 >> 2); + + tmpbuf[j * 3 + 0] = r8; + tmpbuf[j * 3 + 1] = g8; + tmpbuf[j * 3 + 2] = b8; + } + + spi_display_dma_write(&bus->spi_disp, n * 3, tmpbuf); + i += n; + } + + free(tmpbuf); + } + + spi_device_release_bus(bus->spi_disp.handle); +} + +// --- Init sequence interpreter --- + +void dcs_lcd_execute_init_seq(struct SPIDCBus *bus, const uint8_t *seq) +{ + while (*seq != DCS_LCD_INIT_SEQ_END) { + uint8_t cmd = *seq++; + uint8_t flags_len = *seq++; + uint8_t len = flags_len & 0x7F; + + spi_dc_write_cmd_data(bus, cmd, seq, len); + seq += len; + + if (flags_len & DCS_LCD_INIT_SEQ_DELAY) { + vTaskDelay(*seq++ / portTICK_PERIOD_MS); + } + } +} + +// --- Built-in init sequences --- +// +// Each row: CMD, FLAGS_LEN, DATA... (see dcs_lcd_commands.h for format). +// Transcribed mechanically from the original display_init_*() functions. +// _Static_assert on sizeof guards against miscounted data bytes. + +// clang-format off + +// ILI9341 — from display_init_9341() in ili934x_display_driver.c +// 21 command groups, 3 delays (SWRESET 5ms, SLPOUT 120ms, DISPON 120ms). +// SLPOUT intentionally follows the vendor power/voltage commands: the +// internal booster must wake up with the configured VMCTR/PWCTR values, +// otherwise many ILI9341 modules never produce usable output. +const uint8_t dcs_lcd_init_seq_ili9341[] = { + 0x01, DCS_LCD_INIT_SEQ_DELAY | 0, 5, // SWRESET +5ms (0) + 0xEF, 3, 0x03, 0x80, 0x02, // (3) + 0xCF, 3, 0x00, 0xC1, 0x30, // Power ctrl B (3) + 0xED, 4, 0x64, 0x03, 0x12, 0x81, // Power on seq (4) + 0xE8, 3, 0x85, 0x00, 0x78, // Driver timing A (3) + 0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02, // Power ctrl A (5) + 0xF7, 1, 0x20, // Pump ratio (1) + 0xEA, 2, 0x00, 0x00, // Driver timing B (2) + 0xC0, 1, 0x23, // PWCTR1 (1) + 0xC1, 1, 0x10, // PWCTR2 (1) + 0xC5, 2, 0x3E, 0x28, // VMCTR1 (2) + 0xC7, 1, 0x86, // VMCTR2 (1) + 0x36, 1, 0x08, // MADCTL (1) + 0x3A, 1, 0x55, // COLMOD (1) + 0xB1, 2, 0x00, 0x13, // FRMCTR1 (2) + 0xB6, 3, 0x0A, 0xA2, 0x27, // DFUNCTR (3) + 0xF2, 1, 0x00, // Gamma fn dis (1) + 0x26, 1, 0x01, // GAMMASET (1) + 0xE0, 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, // GMCTRP1 (15) + 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, + 0xE1, 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, // GMCTRN1 (15) + 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, + 0x11, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // SLPOUT +120ms (0) + 0x29, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // DISPON +120ms (0) + DCS_LCD_INIT_SEQ_END +}; +_Static_assert(sizeof(dcs_lcd_init_seq_ili9341) == 113, + "ili9341 init sequence size mismatch"); + +// ILI9342C — from display_init_9342c() in ili934x_display_driver.c +// 12 command groups, 3 delays (SWRESET 5ms, SLPOUT 120ms, DISPON 120ms). +// SLPOUT follows the vendor power commands; see ILI9341 seq for details. +const uint8_t dcs_lcd_init_seq_ili9342c[] = { + 0x01, DCS_LCD_INIT_SEQ_DELAY | 0, 5, // SWRESET +5ms (0) + 0xC8, 3, 0xFF, 0x93, 0x42, // Vendor enable (3) + 0xC0, 2, 0x12, 0x12, // PWCTR1 (2) + 0xC1, 1, 0x03, // PWCTR2 (1) + 0xB0, 1, 0xE0, // (1) + 0xF6, 3, 0x00, 0x01, 0x01, // Interface ctrl (3) + 0x36, 1, 0xA0, // MADCTL MY|MV (1) + 0x3A, 1, 0x55, // COLMOD (1) + 0xB6, 3, 0x08, 0x82, 0x27, // DFUNCTR (3) + 0xE0, 15, 0x00, 0x0C, 0x11, 0x04, 0x11, 0x08, 0x37, 0x89, // GMCTRP1 (15) + 0x4C, 0x06, 0x0C, 0x0A, 0x2E, 0x34, 0x0F, + 0xE1, 15, 0x00, 0x0B, 0x11, 0x05, 0x13, 0x09, 0x33, 0x67, // GMCTRN1 (15) + 0x48, 0x07, 0x0E, 0x0B, 0x2E, 0x33, 0x0F, + 0x11, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // SLPOUT +120ms (0) + 0x29, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // DISPON +120ms (0) + DCS_LCD_INIT_SEQ_END +}; +_Static_assert(sizeof(dcs_lcd_init_seq_ili9342c) == 75, + "ili9342c init sequence size mismatch"); + +// ILI9486 — from display_init_9486() in ili948x_display_driver.c +// 9 command groups, 3 delays (SWRESET 5ms, SLPOUT 120ms, DISPON 120ms). +// SLPOUT follows the vendor power commands; see ILI9341 seq for details. +const uint8_t dcs_lcd_init_seq_ili9486[] = { + 0x01, DCS_LCD_INIT_SEQ_DELAY | 0, 5, // SWRESET +5ms (0) + 0xB0, 1, 0x00, // IFMODE (1) + 0x3A, 1, 0x55, // COLMOD (1) + 0xC2, 1, 0x44, // PWRCTR3 (1) + 0xC5, 4, 0x00, 0x00, 0x00, 0x00, // VMCTR1 (4) + 0xE0, 15, 0x0F, 0x1F, 0x1C, 0x0C, 0x0F, 0x08, 0x48, 0x98, // PGAMCTRL (15) + 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00, + 0xE1, 15, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, // NGAMCTRL (15) + 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00, + 0xE2, 15, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, // DGAMCTRL (15) + 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00, + 0x11, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // SLPOUT +120ms (0) + 0x29, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // DISPON +120ms (0) + DCS_LCD_INIT_SEQ_END +}; +_Static_assert(sizeof(dcs_lcd_init_seq_ili9486) == 76, + "ili9486 init sequence size mismatch"); + +// ILI9488 — from display_init_9488() in ili948x_display_driver.c +// 16 command groups, 3 delays (SWRESET 5ms, SLPOUT 120ms, DISPON 120ms). +// RGB666 (COLMOD 0x66, 3 bytes/pixel). +// SLPOUT follows the vendor power commands; see ILI9341 seq for details. +const uint8_t dcs_lcd_init_seq_ili9488[] = { + 0x01, DCS_LCD_INIT_SEQ_DELAY | 0, 5, // SWRESET +5ms (0) + 0xB0, 1, 0x00, // IFMODE (1) + 0xF7, 4, 0xA9, 0x51, 0x2C, 0x82, // ADJCTRL3 (4) + 0xC0, 2, 0x11, 0x09, // PWRCTR1 (2) + 0xC1, 1, 0x41, // PWRCTR2 (1) + 0xC5, 3, 0x00, 0x0A, 0x80, // VMCTR1 (3) + 0xB1, 2, 0xB0, 0x11, // FRMCTR1 (2) + 0xB4, 1, 0x02, // INVCTR (1) + 0xB6, 2, 0x02, 0x02, // DFUNCTR (2) + 0xB7, 1, 0xC6, // ETMOD (1) + 0xBE, 2, 0x00, 0x04, // HS lanes ctrl (2) + 0xE9, 1, 0x00, // Image fn (1) + 0x3A, 1, 0x66, // COLMOD RGB666 (1) + 0xE0, 15, 0x00, 0x07, 0x10, 0x09, 0x17, 0x0B, 0x41, 0x89, // PGAMCTRL (15) + 0x4B, 0x0A, 0x0C, 0x0E, 0x18, 0x1B, 0x0F, + 0xE1, 15, 0x00, 0x17, 0x1A, 0x04, 0x0E, 0x06, 0x2F, 0x45, // NGAMCTRL (15) + 0x43, 0x02, 0x0A, 0x09, 0x32, 0x36, 0x0F, + 0x11, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // SLPOUT +120ms (0) + 0x29, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // DISPON +120ms (0) + DCS_LCD_INIT_SEQ_END +}; +_Static_assert(sizeof(dcs_lcd_init_seq_ili9488) == 89, + "ili9488 init sequence size mismatch"); + +// ST7789 standard — from display_init_std() in st7789_display_driver.c +// 21 command groups, 4 delays (SWRESET 5ms, SLPOUT 120ms, COLMOD 10ms, +// DISPON 120ms). +const uint8_t dcs_lcd_init_seq_st7789_std[] = { + 0x01, DCS_LCD_INIT_SEQ_DELAY | 0, 5, // SWRESET +5ms (0) + 0x11, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // SLPOUT +120ms (0) + 0x13, 0, // NORON (0) + 0x36, 1, 0x00, // MADCTL (1) + 0xB6, 2, 0x0A, 0x82, // (2) + 0xB0, 2, 0x00, 0xE0, // RAMCTRL (2) + 0x3A, DCS_LCD_INIT_SEQ_DELAY | 1, 0x55, 10, // COLMOD +10ms (1) + 0xB2, 5, 0x0C, 0x0C, 0x00, 0x33, 0x33, // PORCTRL (5) + 0xB7, 1, 0x35, // GCTRL (1) + 0xBB, 1, 0x28, // VCOMS (1) + 0xC0, 1, 0x0C, // LCMCTRL (1) + 0xC2, 2, 0x01, 0xFF, // VDVVRHEN (2) + 0xC3, 1, 0x10, // VRHS (1) + 0xC4, 1, 0x20, // VDVSET (1) + 0xC6, 1, 0x0F, // FRCTR2 (1) + 0xD0, 2, 0xA4, 0xA1, // PWCTRL1 (2) + 0xE0, 14, 0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32, 0x44, // PVGAMCTRL (14) + 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17, + 0xE1, 14, 0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31, 0x54, // NVGAMCTRL (14) + 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E, + 0x2A, 4, 0x00, 0x00, 0x00, 0xEF, // CASET 0-239 (4) + 0x2B, 4, 0x00, 0x00, 0x01, 0x3F, // PASET 0-319 (4) + 0x29, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // DISPON +120ms (0) + DCS_LCD_INIT_SEQ_END +}; +_Static_assert(sizeof(dcs_lcd_init_seq_st7789_std) == 104, + "st7789 std init sequence size mismatch"); + +// ST7789 alt gamma 2 — from display_init_alt_gamma_2() in st7789_display_driver.c +// 19 command groups, 4 delays (SWRESET 5ms, SLPOUT 120ms, COLMOD 10ms, +// DISPON 120ms). +const uint8_t dcs_lcd_init_seq_st7789_alt[] = { + 0x01, DCS_LCD_INIT_SEQ_DELAY | 0, 5, // SWRESET +5ms (0) + 0x11, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // SLPOUT +120ms (0) + 0x13, 0, // NORON (0) + 0x36, 1, 0x00, // MADCTL (1) + 0x3A, DCS_LCD_INIT_SEQ_DELAY | 1, 0x55, 10, // COLMOD +10ms (1) + 0xB2, 5, 0x0C, 0x0C, 0x00, 0x33, 0x33, // PORCTRL (5) + 0xB7, 1, 0x75, // GCTRL (1) + 0xBB, 1, 0x1A, // VCOMS (1) + 0xC0, 1, 0x2C, // LCMCTRL (1) + 0xC2, 1, 0x01, // VDVVRHEN (1) + 0xC3, 1, 0x13, // VRHS (1) + 0xC4, 1, 0x20, // VDVSET (1) + 0xC6, 1, 0x0F, // FRCTR2 (1) + 0xD0, 2, 0xA4, 0xA1, // PWCTRL1 (2) + 0xE0, 14, 0xD0, 0x0D, 0x14, 0x0D, 0x0D, 0x09, 0x38, 0x44, // PVGAMCTRL (14) + 0x4E, 0x3A, 0x17, 0x18, 0x2F, 0x30, + 0xE1, 14, 0xD0, 0x09, 0x0F, 0x08, 0x07, 0x14, 0x37, 0x44, // NVGAMCTRL (14) + 0x4D, 0x38, 0x15, 0x16, 0x2C, 0x3E, + 0x2A, 4, 0x00, 0x00, 0x00, 0xEF, // CASET 0-239 (4) + 0x2B, 4, 0x00, 0x00, 0x01, 0x3F, // PASET 0-319 (4) + 0x29, DCS_LCD_INIT_SEQ_DELAY | 0, 120, // DISPON +120ms (0) + DCS_LCD_INIT_SEQ_END +}; +_Static_assert(sizeof(dcs_lcd_init_seq_st7789_alt) == 95, + "st7789 alt gamma 2 init sequence size mismatch"); + +// clang-format on + +// --- Per-controller descriptors --- +// +// madctl[4] values come from the current set_rotation() behavior: +// 0xFF marks a rotation that the existing driver did not validate. +// ILI948x has all 4 rotations (madctl_bgr ORed in at runtime from driver +// state, so the table stores the base value without BGR). +// ILI934x / ST7789 only support rotation 0 (init-default MADCTL) and +// rotation 1 (overridden by set_rotation). + +const struct DCSLCDDesc dcs_lcd_desc_ili9341 = { + .name = "ILI9341", + .native_width = 240, + .native_height = 320, + .spi_clock_hz = 27000000, + .pixel_bytes = 2, + .colmod_value = 0x55, + .madctl = { 0x08, 0xA8, 0xFF, 0xFF }, + .default_bgr = true, + .default_init_seq = dcs_lcd_init_seq_ili9341, + .init_fn = NULL, +}; + +const struct DCSLCDDesc dcs_lcd_desc_ili9342c = { + .name = "ILI9342C", + .native_width = 320, + .native_height = 240, + .spi_clock_hz = 27000000, + .pixel_bytes = 2, + .colmod_value = 0x55, + .madctl = { 0xA0, 0xA8, 0xFF, 0xFF }, + .default_bgr = false, + .default_init_seq = dcs_lcd_init_seq_ili9342c, + .init_fn = NULL, +}; + +const struct DCSLCDDesc dcs_lcd_desc_ili9486 = { + .name = "ILI9486", + .native_width = 320, + .native_height = 480, + .spi_clock_hz = 27000000, + .pixel_bytes = 2, + .colmod_value = 0x55, + .madctl = { 0x40, 0x20, 0x80, 0xE0 }, + .default_bgr = true, + .default_init_seq = dcs_lcd_init_seq_ili9486, + .init_fn = NULL, +}; + +const struct DCSLCDDesc dcs_lcd_desc_ili9488 = { + .name = "ILI9488", + .native_width = 320, + .native_height = 480, + .spi_clock_hz = 27000000, + .pixel_bytes = 3, + .colmod_value = 0x66, + .madctl = { 0x40, 0x20, 0x80, 0xE0 }, + .default_bgr = true, + .default_init_seq = dcs_lcd_init_seq_ili9488, + .init_fn = NULL, +}; + +const struct DCSLCDDesc dcs_lcd_desc_st7789 = { + .name = "ST7789", + .native_width = 240, + .native_height = 320, + .spi_clock_hz = 40000000, + .pixel_bytes = 2, + .colmod_value = 0x55, + .madctl = { 0x00, 0x60, 0xFF, 0xFF }, + .default_bgr = false, + .default_init_seq = dcs_lcd_init_seq_st7789_std, + .init_fn = NULL, +}; + +// ST7796 currently routes through the ST7789 driver and uses the same +// init sequence. It may diverge in a future session. +const struct DCSLCDDesc dcs_lcd_desc_st7796 = { + .name = "ST7796", + .native_width = 320, + .native_height = 480, + .spi_clock_hz = 40000000, + .pixel_bytes = 2, + .colmod_value = 0x55, + .madctl = { 0x00, 0x60, 0xFF, 0xFF }, + .default_bgr = false, + .default_init_seq = dcs_lcd_init_seq_st7789_std, + .init_fn = NULL, +}; diff --git a/dcs_lcd_commands.h b/dcs_lcd_commands.h new file mode 100644 index 0000000..fbee5b1 --- /dev/null +++ b/dcs_lcd_commands.h @@ -0,0 +1,114 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2020-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _DCS_LCD_COMMANDS_H_ +#define _DCS_LCD_COMMANDS_H_ + +#include +#include + +#include "dcs_lcd_screen.h" +#include "spi_dc_driver.h" + +// Generic MIPI DCS command set (subset used by AtomGL DCS LCD drivers). +// Vendor-specific init sequence bytes (ILI9341_FRMCTR1, ST7789_PORCTRL, +// ILI948X_HS_LANES_CTRL, etc.) stay in each driver's header block. +#define DCS_LCD_SWRESET 0x01 +#define DCS_LCD_SLPIN 0x10 +#define DCS_LCD_SLPOUT 0x11 +#define DCS_LCD_NORON 0x13 +#define DCS_LCD_INVOFF 0x20 +#define DCS_LCD_INVON 0x21 +#define DCS_LCD_DISPOFF 0x28 +#define DCS_LCD_DISPON 0x29 +#define DCS_LCD_CASET 0x2A +#define DCS_LCD_PASET 0x2B +#define DCS_LCD_RAMWR 0x2C +#define DCS_LCD_MADCTL 0x36 +#define DCS_LCD_COLMOD 0x3A + +// MADCTL bit positions. +#define DCS_LCD_MAD_MY 0x80 +#define DCS_LCD_MAD_MX 0x40 +#define DCS_LCD_MAD_MV 0x20 +#define DCS_LCD_MAD_ML 0x10 +#define DCS_LCD_MAD_BGR 0x08 + +void dcs_lcd_set_paint_area(struct SPIDCBus *bus, const struct DCSLCDScreen *screen, + int x, int y, int width, int height); + +void dcs_lcd_draw_buffer(struct SPIDCBus *bus, const struct DCSLCDScreen *screen, + int pixel_bytes, int x, int y, int width, int height, const void *imgdata); + +// --- Init sequence byte-array format --- +// +// Each entry: [CMD] [FLAGS_LEN] [DATA_0 ... DATA_N] [DELAY_MS] +// CMD: command byte (0x01-0xFF) +// FLAGS_LEN: bits 6:0 = data byte count (0-127) +// bit 7 = delay flag (DELAY_MS byte follows data) +// DELAY_MS: delay in milliseconds (0-255), present only if flag set +// +// End marker: single DCS_LCD_INIT_SEQ_END (0x00) byte. + +#define DCS_LCD_INIT_SEQ_END 0x00 +#define DCS_LCD_INIT_SEQ_DELAY 0x80 + +void dcs_lcd_execute_init_seq(struct SPIDCBus *bus, const uint8_t *seq); + +// Built-in init sequences. +extern const uint8_t dcs_lcd_init_seq_ili9341[]; +extern const uint8_t dcs_lcd_init_seq_ili9342c[]; +extern const uint8_t dcs_lcd_init_seq_ili9486[]; +extern const uint8_t dcs_lcd_init_seq_ili9488[]; +extern const uint8_t dcs_lcd_init_seq_st7789_std[]; +extern const uint8_t dcs_lcd_init_seq_st7789_alt[]; + +// --- Per-controller descriptor --- +// +// Describes a DCS LCD controller variant so a unified driver can dispatch +// on compatible string. madctl[4] holds per-rotation MADCTL values (0xFF +// for rotations not validated on the current hardware). init_fn is a +// fallback when a byte-array init sequence is insufficient; NULL for all +// current controllers. + +struct DCSLCDDriver; // forward declaration for init_fn + +struct DCSLCDDesc +{ + const char *name; + int native_width; + int native_height; + int spi_clock_hz; + uint8_t pixel_bytes; + uint8_t colmod_value; + uint8_t madctl[4]; + bool default_bgr; + const uint8_t *default_init_seq; + void (*init_fn)(struct DCSLCDDriver *); +}; + +extern const struct DCSLCDDesc dcs_lcd_desc_ili9341; +extern const struct DCSLCDDesc dcs_lcd_desc_ili9342c; +extern const struct DCSLCDDesc dcs_lcd_desc_ili9486; +extern const struct DCSLCDDesc dcs_lcd_desc_ili9488; +extern const struct DCSLCDDesc dcs_lcd_desc_st7789; +extern const struct DCSLCDDesc dcs_lcd_desc_st7796; + +#endif diff --git a/dcs_lcd_display_driver.c b/dcs_lcd_display_driver.c new file mode 100644 index 0000000..482350d --- /dev/null +++ b/dcs_lcd_display_driver.c @@ -0,0 +1,463 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2020-2024 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "display_driver.h" + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "backlight_gpio.h" +#include "dcs_lcd_color.h" +#include "dcs_lcd_commands.h" +#include "dcs_lcd_draw.h" +#include "dcs_lcd_screen.h" +#include "display_common.h" +#include "display_items.h" +#include "display_message.h" +#include "display_task.h" +#include "image_helpers.h" +#include "spi_dc_driver.h" +#include "spi_display.h" + +#define SPI_MODE 0 + +#include "font_data.h" + +static const char *TAG = "dcs_lcd_display_driver"; + +static inline void delay(int ms) +{ + vTaskDelay(ms / portTICK_PERIOD_MS); +} + +struct DCSLCDDriver +{ + struct SPIDCBus bus; + int reset_gpio; + + avm_int_t rotation; + bool madctl_bgr; + + const struct DCSLCDDesc *desc; + + struct DCSLCDScreen screen; + + Context *ctx; + + struct DisplayTaskArgs display_args; +}; + +static const struct { + const char *compat; + const struct DCSLCDDesc *desc; +} dcs_lcd_compat_table[] = { + { "ilitek,ili9341", &dcs_lcd_desc_ili9341 }, + { "ilitek,ili9342c", &dcs_lcd_desc_ili9342c }, + { "ilitek,ili9486", &dcs_lcd_desc_ili9486 }, + { "ilitek,ili9488", &dcs_lcd_desc_ili9488 }, + { "sitronix,st7789", &dcs_lcd_desc_st7789 }, + { "sitronix,st7796", &dcs_lcd_desc_st7796 }, +}; + +static const struct DCSLCDDesc *dcs_lcd_desc_for_compatible(const char *compat) +{ + for (size_t i = 0; i < sizeof(dcs_lcd_compat_table) / sizeof(dcs_lcd_compat_table[0]); i++) { + if (!strcmp(compat, dcs_lcd_compat_table[i].compat)) { + return dcs_lcd_compat_table[i].desc; + } + } + return NULL; +} + +// ILI9488 scanline conversion: RGB565 -> RGB888 bytes. +static inline void rgb565_swapped_line_to_rgb888(uint8_t *dst, const uint16_t *src_swapped, int n_pixels) +{ + for (int i = 0; i < n_pixels; i++) { + uint16_t px = (uint16_t) SPI_SWAP_DATA_TX(src_swapped[i], 16); + + uint8_t r5 = (px >> 11) & 0x1F; + uint8_t g6 = (px >> 5) & 0x3F; + uint8_t b5 = (px >> 0) & 0x1F; + + uint8_t r8 = (r5 << 3) | (r5 >> 2); + uint8_t g8 = (g6 << 2) | (g6 >> 4); + uint8_t b8 = (b5 << 3) | (b5 >> 2); + + dst[i * 3 + 0] = r8; + dst[i * 3 + 1] = g8; + dst[i * 3 + 2] = b8; + } +} + +#define DCS_LCD_DRIVER_FROM_CTX(ctx) \ + CONTAINER_OF((struct DisplayTaskArgs *) (ctx)->platform_data, struct DCSLCDDriver, display_args) + +static void display_init(Context *ctx, term opts); +static void display_init_using_list(struct DCSLCDDriver *driver, term init_list); + +static void do_update(Context *ctx, term display_list) +{ + int proper; + int len = term_list_length(display_list, &proper); + + BaseDisplayItem *items = malloc(sizeof(BaseDisplayItem) * len); + + term t = display_list; + for (int i = 0; i < len; i++) { + display_items_init_item(&items[i], term_get_list_head(t), ctx); + t = term_get_list_tail(t); + } + + struct DCSLCDDriver *driver = DCS_LCD_DRIVER_FROM_CTX(ctx); + int screen_width = driver->screen.w; + int screen_height = driver->screen.h; + + dcs_lcd_set_paint_area(&driver->bus, &driver->screen, 0, 0, screen_width, screen_height); + spi_dc_write_command(&driver->bus, DCS_LCD_RAMWR); + spi_device_acquire_bus(driver->bus.spi_disp.handle, portMAX_DELAY); + + bool transaction_in_progress = false; + + for (int ypos = 0; ypos < screen_height; ypos++) { + int xpos = 0; + while (xpos < screen_width) { + int drawn_pixels = dcs_lcd_draw_x(&driver->screen, xpos, ypos, items, len); + xpos += drawn_pixels; + } + + if (transaction_in_progress) { + spi_transaction_t *trans; + // I did a quick measurement, and most of the time is spent waiting for DMA transaction + // eg. 23 us spent in draw_x, 188 us spent in spi_device_get_trans_result + spi_device_get_trans_result(driver->bus.spi_disp.handle, &trans, portMAX_DELAY); + } + + // Swap scanline buffers. + void *tmp = driver->screen.pixels; + driver->screen.pixels = driver->screen.pixels_out; + driver->screen.pixels_out = tmp; + + if (driver->desc->pixel_bytes == 2) { + spi_display_dma_write(&driver->bus.spi_disp, screen_width * sizeof(uint16_t), driver->screen.pixels_out); + } else { + void *tmpb = driver->screen.bytes; + driver->screen.bytes = driver->screen.bytes_out; + driver->screen.bytes_out = tmpb; + + rgb565_swapped_line_to_rgb888(driver->screen.bytes_out, driver->screen.pixels_out, screen_width); + spi_display_dma_write(&driver->bus.spi_disp, screen_width * 3, driver->screen.bytes_out); + } + + transaction_in_progress = true; + } + + if (transaction_in_progress) { + spi_transaction_t *trans; + spi_device_get_trans_result(driver->bus.spi_disp.handle, &trans, portMAX_DELAY); + } + + spi_device_release_bus(driver->bus.spi_disp.handle); + + display_items_delete(items, len); +} + +static void process_message(Message *message, Context *ctx) +{ + GenMessage gen_message; + if (UNLIKELY(port_parse_gen_message(message->message, &gen_message) != GenCallMessage)) { + fprintf(stderr, "Received invalid message."); + AVM_ABORT(); + } + + term req = gen_message.req; + if (UNLIKELY(!term_is_tuple(req) || term_get_tuple_arity(req) < 1)) { + AVM_ABORT(); + } + term cmd = term_get_tuple_element(req, 0); + + struct DCSLCDDriver *driver = DCS_LCD_DRIVER_FROM_CTX(ctx); + + if (cmd == context_make_atom(ctx, "\x6" + "update")) { + term display_list = term_get_tuple_element(req, 1); + do_update(ctx, display_list); + + } else if (cmd == context_make_atom(ctx, "\xB" + "draw_buffer")) { + int x = term_to_int(term_get_tuple_element(req, 1)); + int y = term_to_int(term_get_tuple_element(req, 2)); + int width = term_to_int(term_get_tuple_element(req, 3)); + int height = term_to_int(term_get_tuple_element(req, 4)); + unsigned long addr_low = term_to_int(term_get_tuple_element(req, 5)); + unsigned long addr_high = term_to_int(term_get_tuple_element(req, 6)); + + const void *data = (const void *) ((addr_low | (addr_high << 16))); + + dcs_lcd_draw_buffer(&driver->bus, &driver->screen, driver->desc->pixel_bytes, x, y, width, height, data); + + // draw_buffer is a kind of cast, no need to reply + return; + + } else if (cmd == globalcontext_make_atom(ctx->global, "\xA" "load_image")) { + handle_load_image(req, gen_message.ref, gen_message.pid, ctx); + return; + + } else { + fprintf(stderr, "display: "); + term_display(stderr, req, ctx); + fprintf(stderr, "\n"); + } + + BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2) + REF_SIZE, heap); + term return_tuple = term_alloc_tuple(2, &heap); + term_put_tuple_element(return_tuple, 0, gen_message.ref); + term_put_tuple_element(return_tuple, 1, OK_ATOM); + + display_message_send(gen_message.pid, return_tuple, ctx->global); + END_WITH_STACK_HEAP(heap, ctx->global); +} + +static void set_rotation(struct DCSLCDDriver *driver, int rotation) +{ + uint8_t madctl = driver->desc->madctl[rotation & 3]; + if (driver->madctl_bgr) { + madctl |= DCS_LCD_MAD_BGR; + } + spi_dc_write_command(&driver->bus, DCS_LCD_MADCTL); + spi_dc_write_data(&driver->bus, madctl); +} + +Context *dcs_lcd_display_create_port(GlobalContext *global, term opts) +{ + Context *ctx = context_new(global); + ctx->native_handler = display_task_consume_mailbox; + display_init(ctx, opts); + return ctx; +} + +static void display_init(Context *ctx, term opts) +{ + // Resolve compatible string -> per-controller descriptor. + term compat_term = interop_kv_get_value_default( + opts, ATOM_STR("\xA", "compatible"), term_nil(), ctx->global); + int str_ok; + char *compat_string = interop_term_to_string(compat_term, &str_ok); + const struct DCSLCDDesc *desc = NULL; + if (str_ok && compat_string) { + desc = dcs_lcd_desc_for_compatible(compat_string); + } + if (!desc) { + ESP_LOGE(TAG, "Failed init: unknown or missing compatible '%s'.", + compat_string ? compat_string : "(null)"); + free(compat_string); + return; + } + free(compat_string); + + term rotation_term = interop_kv_get_value_default( + opts, ATOM_STR("\x8", "rotation"), term_from_int(0), ctx->global); + bool ok = term_is_integer(rotation_term); + avm_int_t rotation = ok ? term_to_int(rotation_term) : 0; + + // Default geometry from descriptor, swapped on odd rotation, overrideable. + int default_w = (rotation & 1) ? desc->native_height : desc->native_width; + int default_h = (rotation & 1) ? desc->native_width : desc->native_height; + term width_term = interop_kv_get_value_default( + opts, ATOM_STR("\x5", "width"), term_from_int(default_w), ctx->global); + term height_term = interop_kv_get_value_default( + opts, ATOM_STR("\x6", "height"), term_from_int(default_h), ctx->global); + + struct DCSLCDDriver *driver = calloc(1, sizeof(struct DCSLCDDriver)); + driver->desc = desc; + driver->rotation = rotation; + driver->madctl_bgr = desc->default_bgr; + driver->screen.w = term_to_int(width_term); + driver->screen.h = term_to_int(height_term); + driver->screen.pixels = heap_caps_malloc(driver->screen.w * sizeof(uint16_t), MALLOC_CAP_DMA); + driver->screen.pixels_out = heap_caps_malloc(driver->screen.w * sizeof(uint16_t), MALLOC_CAP_DMA); + if (desc->pixel_bytes == 3) { + driver->screen.bytes = heap_caps_malloc(driver->screen.w * 3, MALLOC_CAP_DMA); + driver->screen.bytes_out = heap_caps_malloc(driver->screen.w * 3, MALLOC_CAP_DMA); + } + + driver->display_args.messages_queue = xQueueCreate(32, sizeof(Message *)); + driver->display_args.process_message_fn = process_message; + driver->display_args.ctx = ctx; + ctx->platform_data = &driver->display_args; + + driver->ctx = ctx; + + struct SPIDisplayConfig spi_config; + spi_display_init_config(&spi_config); + spi_config.mode = SPI_MODE; + spi_config.clock_speed_hz = desc->spi_clock_hz; + spi_display_parse_config(&spi_config, opts, ctx->global); + spi_display_init(&driver->bus.spi_disp, &spi_config); + + ok = ok && display_common_gpio_from_opts(opts, ATOM_STR("\x2", "dc"), &driver->bus.dc_gpio, ctx->global); + + bool reset_configured = true; + if (!display_common_gpio_from_opts(opts, ATOM_STR("\x5", "reset"), &driver->reset_gpio, ctx->global)) { + ESP_LOGI(TAG, "Reset GPIO not configured."); + reset_configured = false; + } + + term invon = interop_kv_get_value_default(opts, ATOM_STR("\x10", "enable_tft_invon"), FALSE_ATOM, ctx->global); + ok = ok && ((invon == TRUE_ATOM) || (invon == FALSE_ATOM)); + bool enable_tft_invon = (invon == TRUE_ATOM); + + // color_order: rgb|bgr (default: per-descriptor) + term color_order_term = interop_kv_get_value_default(opts, ATOM_STR("\xB", "color_order"), term_nil(), ctx->global); + if (color_order_term != term_nil()) { + if (color_order_term == context_make_atom(ctx, "\x3" "rgb")) { + driver->madctl_bgr = false; + } else if (color_order_term == context_make_atom(ctx, "\x3" "bgr")) { + driver->madctl_bgr = true; + } else { + ok = false; + } + } + + term x_off_term = interop_kv_get_value_default( + opts, ATOM_STR("\x8", "x_offset"), term_from_int(0), ctx->global); + term y_off_term = interop_kv_get_value_default( + opts, ATOM_STR("\x8", "y_offset"), term_from_int(0), ctx->global); + + if (term_is_integer(x_off_term) && term_is_integer(y_off_term)) { + driver->screen.x_offset = (int16_t) term_to_int(x_off_term); + driver->screen.y_offset = (int16_t) term_to_int(y_off_term); + } else { + ok = false; + } + + if (UNLIKELY(!ok)) { + ESP_LOGE(TAG, "Failed init: invalid display parameters."); + return; + } + + if (desc->madctl[driver->rotation & 3] == 0xFF) { + ESP_LOGE(TAG, "Failed init: rotation %d not supported by controller %s.", + (int) driver->rotation, desc->name); + return; + } + + // Reset + if (reset_configured) { + spi_device_acquire_bus(driver->bus.spi_disp.handle, portMAX_DELAY); + gpio_set_direction(driver->reset_gpio, GPIO_MODE_OUTPUT); + gpio_set_level(driver->reset_gpio, 1); + vTaskDelay(50 / portTICK_PERIOD_MS); + gpio_set_level(driver->reset_gpio, 0); + vTaskDelay(50 / portTICK_PERIOD_MS); + gpio_set_level(driver->reset_gpio, 1); + spi_device_release_bus(driver->bus.spi_disp.handle); + } + + gpio_set_direction(driver->bus.dc_gpio, GPIO_MODE_OUTPUT); + + // Init sequence: init_list opt overrides everything; otherwise descriptor + // default, with init_seq_type "alt_gamma_2" selecting the ST7789 alt seq. + term maybe_init_list + = interop_kv_get_value_default(opts, ATOM_STR("\x9", "init_list"), term_nil(), ctx->global); + if (maybe_init_list != term_nil()) { + display_init_using_list(driver, maybe_init_list); + } else { + const uint8_t *init_seq = desc->default_init_seq; + term init_seq_type_term = interop_kv_get_value_default( + opts, ATOM_STR("\xD", "init_seq_type"), term_nil(), ctx->global); + if (init_seq_type_term != term_nil()) { + int type_ok; + char *type_str = interop_term_to_string(init_seq_type_term, &type_ok); + if (type_ok && !strcmp(type_str, "alt_gamma_2") + && (desc == &dcs_lcd_desc_st7789 || desc == &dcs_lcd_desc_st7796)) { + init_seq = dcs_lcd_init_seq_st7789_alt; + } + free(type_str); + } + dcs_lcd_execute_init_seq(&driver->bus, init_seq); + } + + set_rotation(driver, driver->rotation); + + spi_dc_write_command(&driver->bus, enable_tft_invon ? DCS_LCD_INVON : DCS_LCD_INVOFF); + + struct BacklightGPIOConfig backlight_config; + backlight_gpio_init_config(&backlight_config); + backlight_gpio_parse_config(&backlight_config, opts, ctx->global); + backlight_gpio_init(&backlight_config); + + xTaskCreate(display_task_process_messages, "display", 10000, &driver->display_args, 1, NULL); +} + +static void display_init_using_list(struct DCSLCDDriver *driver, term init_list) +{ + term t = init_list; + while (term_is_nonempty_list(t)) { + term head = term_get_list_head(t); + if (term_is_tuple(head) && term_get_tuple_arity(head) == 2) { + term cmd_term = term_get_tuple_element(head, 0); + term data_term = term_get_tuple_element(head, 1); + if (term_is_integer(cmd_term) && term_is_binary(data_term)) { + avm_int_t cmd = term_to_int(cmd_term); + const uint8_t *data = (const uint8_t *) term_binary_data(data_term); + spi_dc_write_cmd_data(&driver->bus, cmd, data, term_binary_size(data_term)); + } else if ((cmd_term == context_make_atom(driver->ctx, ATOM_STR("\x8", "sleep_ms"))) + && term_is_integer(data_term)) { + delay(term_to_int(data_term)); + } else { + // invalid + break; + } + } else { + // invalid + break; + } + + t = term_get_list_tail(t); + } + if (t != term_nil()) { + fprintf(stderr, "Invalid init_list!\n"); + } +} diff --git a/dcs_lcd_draw.c b/dcs_lcd_draw.c new file mode 100644 index 0000000..ad03da3 --- /dev/null +++ b/dcs_lcd_draw.c @@ -0,0 +1,275 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2020-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "dcs_lcd_draw.h" + +#include +#include +#include + +#include + +#include "dcs_lcd_color.h" +#include "font_data.h" + +int dcs_lcd_find_max_line_len(const struct DCSLCDScreen *screen, + BaseDisplayItem items[], size_t items_len, int xpos, int ypos) +{ + int line_len = screen->w - xpos; + + for (size_t i = 0; i < items_len; i++) { + BaseDisplayItem *item = &items[i]; + + if ((xpos < item->x) && (ypos >= item->y) && (ypos < item->y + item->height)) { + int len_to_item = item->x - xpos; + line_len = (line_len > len_to_item) ? len_to_item : line_len; + } + } + + return line_len; +} + +int dcs_lcd_draw_image_x(const struct DCSLCDScreen *screen, + int xpos, int ypos, int max_line_len, BaseDisplayItem *item) +{ + int x = item->x; + int y = item->y; + + uint16_t bgcolor = 0; + bool visible_bg; + if (item->brcolor != 0) { + bgcolor = rgba8888_color_to_rgb565(item->brcolor); + visible_bg = true; + } else { + visible_bg = false; + } + + int width = item->width; + const char *data = item->data.image_data.pix; + + int drawn_pixels = 0; + + uint32_t *pixels = ((uint32_t *) data) + (ypos - y) * width + (xpos - x); + uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); + + if (width > xpos - x + max_line_len) { + width = xpos - x + max_line_len; + } + + for (int j = xpos - x; j < width; j++) { + uint32_t img_pixel = READ_32_UNALIGNED(pixels); + uint8_t alpha = rgba8888_get_alpha(img_pixel); + if (alpha == 0xFF) { + uint16_t color = uint32_color_to_surface(img_pixel); + pixmem16[drawn_pixels] = color; + } else if (visible_bg) { + uint16_t color = rgba8888_color_to_rgb565(img_pixel); + uint16_t blended = alpha_blend_rgb565(color, bgcolor, alpha); + pixmem16[drawn_pixels] = rgb565_color_to_surface(blended); + } else { + return drawn_pixels; + } + drawn_pixels++; + pixels++; + } + + return drawn_pixels; +} + +int dcs_lcd_draw_rect_x(const struct DCSLCDScreen *screen, + int xpos, int ypos, int max_line_len, BaseDisplayItem *item) +{ + int x = item->x; + int width = item->width; + uint16_t color = uint32_color_to_surface(item->brcolor); + + int drawn_pixels = 0; + + uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); + + if (width > xpos - x + max_line_len) { + width = xpos - x + max_line_len; + } + + for (int j = xpos - x; j < width; j++) { + pixmem16[drawn_pixels] = color; + drawn_pixels++; + } + + return drawn_pixels; +} + +int dcs_lcd_draw_text_x(const struct DCSLCDScreen *screen, + int xpos, int ypos, int max_line_len, BaseDisplayItem *item) +{ + int x = item->x; + int y = item->y; + uint16_t fgcolor = uint32_color_to_surface(item->data.text_data.fgcolor); + uint16_t bgcolor; + bool visible_bg; + if (item->brcolor != 0) { + bgcolor = uint32_color_to_surface(item->brcolor); + visible_bg = true; + } else { + visible_bg = false; + } + + char *text = (char *) item->data.text_data.text; + + int width = item->width; + + int drawn_pixels = 0; + + uint16_t *pixmem32 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); + + if (width > xpos - x + max_line_len) { + width = xpos - x + max_line_len; + } + + for (int j = xpos - x; j < width; j++) { + int char_index = j / CHAR_WIDTH; + char c = text[char_index]; + unsigned const char *glyph = fontdata + ((unsigned char) c) * 16; + + unsigned char row = glyph[ypos - y]; + + bool opaque; + int k = j % CHAR_WIDTH; + if (row & (1 << (7 - k))) { + opaque = true; + } else { + opaque = false; + } + + if (opaque) { + pixmem32[drawn_pixels] = fgcolor; + } else if (visible_bg) { + pixmem32[drawn_pixels] = bgcolor; + } else { + return drawn_pixels; + } + drawn_pixels++; + } + + return drawn_pixels; +} + +int dcs_lcd_draw_scaled_cropped_img_x(const struct DCSLCDScreen *screen, + int xpos, int ypos, int max_line_len, BaseDisplayItem *item) +{ + int x = item->x; + int y = item->y; + + uint16_t bgcolor = 0; + bool visible_bg; + if (item->brcolor != 0) { + bgcolor = rgba8888_color_to_rgb565(item->brcolor); + visible_bg = true; + } else { + visible_bg = false; + } + + int width = item->width; + const char *data = item->data.image_data_with_size.pix; + + int drawn_pixels = 0; + + int y_scale = item->y_scale; + int x_scale = item->x_scale; + int img_width = item->data.image_data_with_size.width; + + int source_x = item->source_x; + int source_y = item->source_y; + + uint32_t *pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((xpos - x) / x_scale); + uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); + + if (source_x + (width / x_scale) > img_width) { + width = (img_width - source_x) * x_scale; + } + + if (width > xpos - x + max_line_len) { + width = xpos - x + max_line_len; + } + + for (int j = xpos - x; j < width; j++) { + uint32_t img_pixel = READ_32_UNALIGNED(pixels); + uint8_t alpha = rgba8888_get_alpha(img_pixel); + if (alpha == 0xFF) { + uint16_t color = uint32_color_to_surface(img_pixel); + pixmem16[drawn_pixels] = color; + } else if (visible_bg) { + uint16_t color = rgba8888_color_to_rgb565(img_pixel); + uint16_t blended = alpha_blend_rgb565(color, bgcolor, alpha); + pixmem16[drawn_pixels] = rgb565_color_to_surface(blended); + } else { + return drawn_pixels; + } + drawn_pixels++; + pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((j + 1) / x_scale); + } + + return drawn_pixels; +} + +int dcs_lcd_draw_x(const struct DCSLCDScreen *screen, + int xpos, int ypos, BaseDisplayItem items[], size_t items_len) +{ + bool below = false; + + for (size_t i = 0; i < items_len; i++) { + BaseDisplayItem *item = &items[i]; + if ((xpos < item->x) || (xpos >= item->x + item->width) || (ypos < item->y) || (ypos >= item->y + item->height)) { + continue; + } + + int max_line_len = below ? 1 : dcs_lcd_find_max_line_len(screen, items, i, xpos, ypos); + + int drawn_pixels = 0; + switch (items[i].primitive) { + case PrimitiveImage: + drawn_pixels = dcs_lcd_draw_image_x(screen, xpos, ypos, max_line_len, item); + break; + + case PrimitiveRect: + drawn_pixels = dcs_lcd_draw_rect_x(screen, xpos, ypos, max_line_len, item); + break; + + case PrimitiveScaledCroppedImage: + drawn_pixels = dcs_lcd_draw_scaled_cropped_img_x(screen, xpos, ypos, max_line_len, item); + break; + + case PrimitiveText: + drawn_pixels = dcs_lcd_draw_text_x(screen, xpos, ypos, max_line_len, item); + break; + default: { + fprintf(stderr, "unexpected display list command.\n"); + } + } + + if (drawn_pixels != 0) { + return drawn_pixels; + } + + below = true; + } + + return 1; +} diff --git a/dcs_lcd_draw.h b/dcs_lcd_draw.h new file mode 100644 index 0000000..a99b407 --- /dev/null +++ b/dcs_lcd_draw.h @@ -0,0 +1,45 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2020-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _DCS_LCD_DRAW_H_ +#define _DCS_LCD_DRAW_H_ + +#include "dcs_lcd_screen.h" +#include "display_items.h" + +int dcs_lcd_draw_image_x(const struct DCSLCDScreen *screen, + int xpos, int ypos, int max_line_len, BaseDisplayItem *item); + +int dcs_lcd_draw_rect_x(const struct DCSLCDScreen *screen, + int xpos, int ypos, int max_line_len, BaseDisplayItem *item); + +int dcs_lcd_draw_text_x(const struct DCSLCDScreen *screen, + int xpos, int ypos, int max_line_len, BaseDisplayItem *item); + +int dcs_lcd_draw_scaled_cropped_img_x(const struct DCSLCDScreen *screen, + int xpos, int ypos, int max_line_len, BaseDisplayItem *item); + +int dcs_lcd_find_max_line_len(const struct DCSLCDScreen *screen, + BaseDisplayItem items[], size_t items_len, int xpos, int ypos); + +int dcs_lcd_draw_x(const struct DCSLCDScreen *screen, + int xpos, int ypos, BaseDisplayItem items[], size_t items_len); + +#endif diff --git a/dcs_lcd_screen.h b/dcs_lcd_screen.h new file mode 100644 index 0000000..071c1ed --- /dev/null +++ b/dcs_lcd_screen.h @@ -0,0 +1,41 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2020-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _DCS_LCD_SCREEN_H_ +#define _DCS_LCD_SCREEN_H_ + +#include + +// Per-display state shared across the DCS LCD scanline rendering pipeline. +struct DCSLCDScreen +{ + int w; + int h; + int16_t x_offset; + int16_t y_offset; + uint16_t *pixels; + uint16_t *pixels_out; + + // ILI9488: 3 bytes/pixel. + uint8_t *bytes; + uint8_t *bytes_out; +}; + +#endif diff --git a/display_driver.c b/display_driver.c index d229984..bce5df6 100644 --- a/display_driver.c +++ b/display_driver.c @@ -29,12 +29,10 @@ static const char *TAG = "display_driver"; -Context *acep_5in65_7c_display_driver_create_port(GlobalContext *global, term opts); -Context *ili934x_display_create_port(GlobalContext *global, term opts); -Context *ili948x_display_create_port(GlobalContext *global, term opts); +Context *epaper_display_create_port(GlobalContext *global, term opts); +Context *dcs_lcd_display_create_port(GlobalContext *global, term opts); Context *memory_lcd_display_create_port(GlobalContext *global, term opts); -Context *ssd1306_display_create_port(GlobalContext *global, term opts); -Context *st7789_display_create_port(GlobalContext *global, term opts); +Context *oled_display_create_port(GlobalContext *global, term opts); Context *display_create_port(GlobalContext *global, term opts) { @@ -53,27 +51,24 @@ Context *display_create_port(GlobalContext *global, term opts) } Context *ctx = NULL; - if (!strcmp(compat_string, "waveshare,5in65-acep-7c")) { - ctx = acep_5in65_7c_display_driver_create_port(global, opts); + if (!strcmp(compat_string, "waveshare,5in65-acep-7c") + || !strcmp(compat_string, "good-display/gdep073e01")) { + ctx = epaper_display_create_port(global, opts); } else if (!strcmp(compat_string, "sharp,memory-lcd")) { ctx = memory_lcd_display_create_port(global, opts); - } else if (!strcmp(compat_string, "ilitek,ili9341")) { - ctx = ili934x_display_create_port(global, opts); - } else if (!strcmp(compat_string, "ilitek,ili9342c")) { - ctx = ili934x_display_create_port(global, opts); - } else if (!strcmp(compat_string, "ilitek,ili9486")) { - ctx = ili948x_display_create_port(global, opts); - } else if (!strcmp(compat_string, "ilitek,ili9488")) { - ctx = ili948x_display_create_port(global, opts); + } else if (!strcmp(compat_string, "ilitek,ili9341") + || !strcmp(compat_string, "ilitek,ili9342c") + || !strcmp(compat_string, "ilitek,ili9486") + || !strcmp(compat_string, "ilitek,ili9488") + || !strcmp(compat_string, "sitronix,st7789") + || !strcmp(compat_string, "sitronix,st7796")) { + ctx = dcs_lcd_display_create_port(global, opts); } else if (!strcmp(compat_string, "solomon-systech,ssd1306")) { - ctx = ssd1306_display_create_port(global, opts); + ctx = oled_display_create_port(global, opts); } else if (!strcmp(compat_string, "solomon-systech,ssd1315")) { - ctx = ssd1306_display_create_port(global, opts); + ctx = oled_display_create_port(global, opts); } else if (!strcmp(compat_string, "sino-wealth,sh1106")) { - ctx = ssd1306_display_create_port(global, opts); - } else if (!strcmp(compat_string, "sitronix,st7789") - || !strcmp(compat_string, "sitronix,st7796")) { - ctx = st7789_display_create_port(global, opts); + ctx = oled_display_create_port(global, opts); } else { ESP_LOGE(TAG, "No matching display driver for given `comptaible`: `%s`.", compat_string); } diff --git a/display_driver.h b/display_driver.h index ac450e9..a859162 100644 --- a/display_driver.h +++ b/display_driver.h @@ -22,10 +22,8 @@ #define _DISPLAY_DRIVER_H_ #include -#include #include Context *display_create_port(GlobalContext *global, term opts); -void display_enqueue_message(Message *message); #endif diff --git a/display_items.c b/display_items.c new file mode 100644 index 0000000..6794c64 --- /dev/null +++ b/display_items.c @@ -0,0 +1,281 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2020-2022 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "display_items.h" + +#include +#include +#include + +#include + +#ifdef ENABLE_UFONT +#include "ufontlib.h" +extern UFontManager *ufont_manager; + +struct Surface +{ + int width; + int height; + void *buffer; + uint32_t fg_color; // RGBA8888 little-endian byte order with the + // alpha byte cleared; ORed with the per-pixel + // alpha in epd_draw_pixel. +}; + +#define BPP 4 + +void epd_draw_pixel(int xpos, int ypos, uint8_t color, void *buffer) +{ + struct Surface *surface = buffer; + + if (xpos < 0 || ypos < 0 || xpos >= surface->width + || ypos >= surface->height) { + return; + } + + uint32_t *pixel = (uint32_t *) (((uint8_t *) surface->buffer) + + (surface->width * ypos + xpos) * sizeof(uint32_t)); + + // The `color` parameter is the LUT-mapped glyph value from + // draw_char: 0 = full foreground (fg_color=0 in default props), + // 240 = full background (bg_color=15), in steps of 16. Render + // the foreground RGB on transparent with anti-aliased alpha + // derived from the inverted grayscale. + uint8_t alpha = (15 - (color >> 4)) * 17; + *pixel = ((uint32_t) alpha << 24) | (surface->fg_color & 0x00FFFFFFu); +} +#endif /* ENABLE_UFONT */ + +void display_items_init_item(BaseDisplayItem *item, term req, Context *ctx) +{ + item->owns_data = false; + + term cmd = term_get_tuple_element(req, 0); + + if (cmd == context_make_atom(ctx, "\x5" + "image")) { + item->primitive = PrimitiveImage; + item->x = term_to_int(term_get_tuple_element(req, 1)); + item->y = term_to_int(term_get_tuple_element(req, 2)); + + term bgcolor = term_get_tuple_element(req, 3); + if (bgcolor == context_make_atom(ctx, "\xB" + "transparent")) { + item->brcolor = 0; + } else { + item->brcolor = ((uint32_t) term_to_int(bgcolor)) << 8 | 0xFF; + } + + term img = term_get_tuple_element(req, 4); + + term format = term_get_tuple_element(img, 0); + if (format != context_make_atom(ctx, "\x8" + "rgba8888")) { + fprintf(stderr, "unsupported image format: "); + term_display(stderr, format, ctx); + fprintf(stderr, "\n"); + return; + } + item->width = term_to_int(term_get_tuple_element(img, 1)); + item->height = term_to_int(term_get_tuple_element(img, 2)); + item->data.image_data.pix = term_binary_data(term_get_tuple_element(img, 3)); + + } else if (cmd == globalcontext_make_atom(ctx->global, ATOM_STR("\x14", "scaled_cropped_image"))) { + item->primitive = PrimitiveScaledCroppedImage; + item->x = term_to_int(term_get_tuple_element(req, 1)); + item->y = term_to_int(term_get_tuple_element(req, 2)); + item->width = term_to_int(term_get_tuple_element(req, 3)); + item->height = term_to_int(term_get_tuple_element(req, 4)); + + term bgcolor = term_get_tuple_element(req, 5); + if (bgcolor == globalcontext_make_atom(ctx->global, "\xB" + "transparent")) { + item->brcolor = 0; + } else { + item->brcolor = ((uint32_t) term_to_int(bgcolor)) << 8 | 0xFF; + } + + item->source_x = term_to_int(term_get_tuple_element(req, 6)); + item->source_y = term_to_int(term_get_tuple_element(req, 7)); + item->x_scale = term_to_int(term_get_tuple_element(req, 8)); + item->y_scale = term_to_int(term_get_tuple_element(req, 9)); + + // 10th element is for opts, but right now no opts are supported + + term img = term_get_tuple_element(req, 11); + + term format = term_get_tuple_element(img, 0); + if (format != globalcontext_make_atom(ctx->global, "\x8" + "rgba8888")) { + fprintf(stderr, "unsupported image format: "); + term_display(stderr, format, ctx); + fprintf(stderr, "\n"); + return; + } + item->data.image_data_with_size.width = term_to_int(term_get_tuple_element(img, 1)); + item->data.image_data_with_size.height = term_to_int(term_get_tuple_element(img, 2)); + item->data.image_data_with_size.pix = term_binary_data(term_get_tuple_element(img, 3)); + + } else if (cmd == context_make_atom(ctx, "\x4" + "rect")) { + item->primitive = PrimitiveRect; + item->x = term_to_int(term_get_tuple_element(req, 1)); + item->y = term_to_int(term_get_tuple_element(req, 2)); + item->width = term_to_int(term_get_tuple_element(req, 3)); + item->height = term_to_int(term_get_tuple_element(req, 4)); + item->brcolor = term_to_int(term_get_tuple_element(req, 5)) << 8 | 0xFF; + + } else if (cmd == context_make_atom(ctx, "\x4" + "text")) { + item->x = term_to_int(term_get_tuple_element(req, 1)); + item->y = term_to_int(term_get_tuple_element(req, 2)); + uint32_t fgcolor = term_to_int(term_get_tuple_element(req, 4)) << 8 | 0xFF; + uint32_t brcolor; + term bgcolor = term_get_tuple_element(req, 5); + if (bgcolor == globalcontext_make_atom(ctx->global, "\xB" + "transparent")) { + brcolor = 0; + } else { + brcolor = ((uint32_t) term_to_int(bgcolor)) << 8 | 0xFF; + } + term text_term = term_get_tuple_element(req, 6); + int ok; + char *text = interop_term_to_string(text_term, &ok); + if (!ok) { + fprintf(stderr, "invalid text.\n"); + return; + } + + term font = term_get_tuple_element(req, 3); + + if (font == globalcontext_make_atom(ctx->global, "\xB" "default16px")) { + item->primitive = PrimitiveText; + item->height = 16; + item->width = strlen(text) * 8; + item->brcolor = brcolor; + item->data.text_data.fgcolor = fgcolor; + item->data.text_data.text = text; + + } else { +#ifdef ENABLE_UFONT + char *handle = interop_atom_to_string(ctx, font); + EpdFont *loaded_font = NULL; + if (handle != NULL) { + loaded_font = ufont_manager_find_by_handle(ufont_manager, handle); + free(handle); + } + + if (!loaded_font) { + fprintf(stderr, "unsupported font: "); + term_display(stderr, font, ctx); + fprintf(stderr, "\n"); + return; + } + + EpdFontProperties props = epd_font_properties_default(); + EpdRect rect = epd_get_string_rect(loaded_font, text, 0, 0, 0, &props); + + struct Surface surface; + surface.width = rect.width; + surface.height = rect.height; + surface.buffer = malloc(rect.width * rect.height * BPP); + if (!surface.buffer) { + fprintf(stderr, "Failed to allocate ufont surface (%ix%i)\n", + rect.width, rect.height); + free(text); + return; + } + memset(surface.buffer, 0, rect.width * rect.height * BPP); + // Convert Erlang fgcolor (0xRRGGBBAA) to RGBA8888 little- + // endian byte order (R in low byte, alpha byte cleared) so + // epd_draw_pixel can OR it with the per-pixel alpha. + surface.fg_color = ((fgcolor >> 24) & 0xFFu) + | (((fgcolor >> 16) & 0xFFu) << 8) + | (((fgcolor >> 8) & 0xFFu) << 16); + int text_x = 0; + int text_y = loaded_font->ascender; + enum EpdDrawError res = epd_write_default(loaded_font, text, &text_x, &text_y, &surface); + free(text); + if (res != EPD_DRAW_SUCCESS) { + fprintf(stderr, "Failed to draw text. Error code: %i\n", res); + return; + } + + item->primitive = PrimitiveImage; + item->width = surface.width; + item->height = surface.height; + item->brcolor = brcolor; + item->data.image_data.pix = surface.buffer; + item->owns_data = true; +#else + fprintf(stderr, "unsupported font: "); + term_display(stderr, font, ctx); + fprintf(stderr, "\n"); + item->primitive = PrimitiveText; + item->height = 16; + item->width = strlen(text) * 8; + item->brcolor = brcolor; + item->data.text_data.fgcolor = fgcolor; + item->data.text_data.text = text; + +#endif + } + + } else { + fprintf(stderr, "unexpected display list command: "); + term_display(stderr, req, ctx); + fprintf(stderr, "\n"); + + item->primitive = PrimitiveInvalid; + item->x = -1; + item->y = -1; + item->width = 1; + item->height = 1; + } +} + +void display_items_delete(BaseDisplayItem items[], size_t items_len) +{ + for (size_t i = 0; i < items_len; i++) { + BaseDisplayItem *item = &items[i]; + + switch (item->primitive) { + case PrimitiveImage: + if (item->owns_data) { + free((void *) item->data.image_data.pix); + } + break; + + case PrimitiveRect: + break; + + case PrimitiveText: + free((char *) item->data.text_data.text); + break; + + default: { + break; + } + } + } + + free(items); +} diff --git a/display_items.h b/display_items.h index 8d6c1f6..b5d505f 100644 --- a/display_items.h +++ b/display_items.h @@ -18,23 +18,29 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include +#ifndef _DISPLAY_ITEMS_H_ +#define _DISPLAY_ITEMS_H_ + +#include +#include #include +#include + // TODO: deprecated helper, remove this static inline term context_make_atom(Context *ctx, AtomString string) { return globalcontext_make_atom(ctx->global, string); } -enum primitive +typedef enum { - Invalid = 0, - Image, - ScaledCroppedImage, - Rect, - Text -}; + PrimitiveInvalid = 0, + PrimitiveImage, + PrimitiveScaledCroppedImage, + PrimitiveRect, + PrimitiveText +} primitive_t; struct TextData { @@ -56,7 +62,7 @@ struct ImageDataWithSize struct BaseDisplayItem { - enum primitive primitive; + primitive_t primitive; int x; int y; int width; @@ -74,203 +80,14 @@ struct BaseDisplayItem int source_y; int x_scale; int y_scale; + + bool owns_data; }; typedef struct BaseDisplayItem BaseDisplayItem; -static void init_item(BaseDisplayItem *item, term req, Context *ctx) -{ - term cmd = term_get_tuple_element(req, 0); - - if (cmd == context_make_atom(ctx, "\x5" - "image")) { - item->primitive = Image; - item->x = term_to_int(term_get_tuple_element(req, 1)); - item->y = term_to_int(term_get_tuple_element(req, 2)); - - term bgcolor = term_get_tuple_element(req, 3); - if (bgcolor == context_make_atom(ctx, "\xB" - "transparent")) { - item->brcolor = 0; - } else { - item->brcolor = ((uint32_t) term_to_int(bgcolor)) << 8 | 0xFF; - } - - term img = term_get_tuple_element(req, 4); - - term format = term_get_tuple_element(img, 0); - if (format != context_make_atom(ctx, "\x8" - "rgba8888")) { - fprintf(stderr, "unsupported image format: "); - term_display(stderr, format, ctx); - fprintf(stderr, "\n"); - return; - } - item->width = term_to_int(term_get_tuple_element(img, 1)); - item->height = term_to_int(term_get_tuple_element(img, 2)); - item->data.image_data.pix = term_binary_data(term_get_tuple_element(img, 3)); - - } else if (cmd == globalcontext_make_atom(ctx->global, ATOM_STR("\x14", "scaled_cropped_image"))) { - item->primitive = ScaledCroppedImage; - item->x = term_to_int(term_get_tuple_element(req, 1)); - item->y = term_to_int(term_get_tuple_element(req, 2)); - item->width = term_to_int(term_get_tuple_element(req, 3)); - item->height = term_to_int(term_get_tuple_element(req, 4)); - - term bgcolor = term_get_tuple_element(req, 5); - if (bgcolor == globalcontext_make_atom(ctx->global, "\xB" - "transparent")) { - item->brcolor = 0; - } else { - item->brcolor = ((uint32_t) term_to_int(bgcolor)) << 8 | 0xFF; - } - - item->source_x = term_to_int(term_get_tuple_element(req, 6)); - item->source_y = term_to_int(term_get_tuple_element(req, 7)); - item->x_scale = term_to_int(term_get_tuple_element(req, 8)); - item->y_scale = term_to_int(term_get_tuple_element(req, 9)); - - // 10th element is for opts, but right now no opts are supported - - term img = term_get_tuple_element(req, 11); - - term format = term_get_tuple_element(img, 0); - if (format != globalcontext_make_atom(ctx->global, "\x8" - "rgba8888")) { - fprintf(stderr, "unsupported image format: "); - term_display(stderr, format, ctx); - fprintf(stderr, "\n"); - return; - } - item->data.image_data_with_size.width = term_to_int(term_get_tuple_element(img, 1)); - item->data.image_data_with_size.height = term_to_int(term_get_tuple_element(img, 2)); - item->data.image_data_with_size.pix = term_binary_data(term_get_tuple_element(img, 3)); - - } else if (cmd == context_make_atom(ctx, "\x4" - "rect")) { - item->primitive = Rect; - item->x = term_to_int(term_get_tuple_element(req, 1)); - item->y = term_to_int(term_get_tuple_element(req, 2)); - item->width = term_to_int(term_get_tuple_element(req, 3)); - item->height = term_to_int(term_get_tuple_element(req, 4)); - item->brcolor = term_to_int(term_get_tuple_element(req, 5)) << 8 | 0xFF; - - } else if (cmd == context_make_atom(ctx, "\x4" - "text")) { - item->x = term_to_int(term_get_tuple_element(req, 1)); - item->y = term_to_int(term_get_tuple_element(req, 2)); - uint32_t fgcolor = term_to_int(term_get_tuple_element(req, 4)) << 8 | 0xFF; - uint32_t brcolor; - term bgcolor = term_get_tuple_element(req, 5); - if (bgcolor == globalcontext_make_atom(ctx->global, "\xB" - "transparent")) { - brcolor = 0; - } else { - brcolor = ((uint32_t) term_to_int(bgcolor)) << 8 | 0xFF; - } - term text_term = term_get_tuple_element(req, 6); - int ok; - char *text = interop_term_to_string(text_term, &ok); - if (!ok) { - fprintf(stderr, "invalid text.\n"); - return; - } - - term font = term_get_tuple_element(req, 3); - - if (font == globalcontext_make_atom(ctx->global, "\xB" "default16px")) { - item->primitive = Text; - item->height = 16; - item->width = strlen(text) * 8; - item->brcolor = brcolor; - item->data.text_data.fgcolor = fgcolor; - item->data.text_data.text = text; - - } else { -#ifdef ENABLE_UFONT - AtomString handle_atom = globalcontext_atomstring_from_term(ctx->global, font); - char handle[255]; - atom_string_to_c(handle_atom, handle, sizeof(handle)); - EpdFont *loaded_font = ufont_manager_find_by_handle(ufont_manager, handle); - - if (!loaded_font) { - fprintf(stderr, "unsupported font: "); - term_display(stderr, font, ctx); - fprintf(stderr, "\n"); - return; - } - - EpdFontProperties props = epd_font_properties_default(); - EpdRect rect = epd_get_string_rect(loaded_font, text, 0, 0, 0, &props); - - struct Surface surface; - surface.width = rect.width; - surface.height = rect.height; - surface.buffer = malloc(rect.width * rect.height * BPP); - memset(surface.buffer, 0, rect.width * rect.height * BPP); - int text_x = 0; - int text_y = loaded_font->ascender; - enum EpdDrawError res = epd_write_default(loaded_font, text, &text_x, &text_y, &surface); - free(text); - if (res != EPD_DRAW_SUCCESS) { - fprintf(stderr, "Failed to draw text. Error code: %i\n", res); - return; - } - - item->primitive = Image; - item->width = surface.width; - item->height = surface.height; - item->brcolor = 0; - //FIXME: surface buffer leak - item->data.image_data.pix = surface.buffer; -#else - fprintf(stderr, "unsupported font: "); - term_display(stderr, font, ctx); - fprintf(stderr, "\n"); - item->primitive = Text; - item->height = 16; - item->width = strlen(text) * 8; - item->brcolor = brcolor; - item->data.text_data.fgcolor = fgcolor; - item->data.text_data.text = text; +void display_items_init_item(BaseDisplayItem *item, term req, Context *ctx); +void display_items_delete(BaseDisplayItem items[], size_t items_len); #endif - } - - } else { - fprintf(stderr, "unexpected display list command: "); - term_display(stderr, req, ctx); - fprintf(stderr, "\n"); - item->primitive = Invalid; - item->x = -1; - item->y = -1; - item->width = 1; - item->height = 1; - } -} - -static void destroy_items(BaseDisplayItem *items, int items_count) -{ - for (int i = 0; i < items_count; i++) { - BaseDisplayItem *item = &items[i]; - - switch (item->primitive) { - case Image: - break; - - case Rect: - break; - - case Text: - free((char *) item->data.text_data.text); - break; - - default: { - break; - } - } - } - - free(items); -} diff --git a/display_message.c b/display_message.c new file mode 100644 index 0000000..b810034 --- /dev/null +++ b/display_message.c @@ -0,0 +1,27 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2022 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "display_message.h" + +void display_message_send(term pid, term message, GlobalContext *global) +{ + int local_process_id = term_to_local_process_id(pid); + globalcontext_send_message(global, local_process_id, message); +} diff --git a/display_message.h b/display_message.h new file mode 100644 index 0000000..16fc1d7 --- /dev/null +++ b/display_message.h @@ -0,0 +1,29 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2022 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _DISPLAY_MESSAGE_H_ +#define _DISPLAY_MESSAGE_H_ + +#include +#include + +void display_message_send(term pid, term message, GlobalContext *global); + +#endif diff --git a/display_task.c b/display_task.c new file mode 100644 index 0000000..c69d6b7 --- /dev/null +++ b/display_task.c @@ -0,0 +1,120 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "display_task.h" + +#include +#include +#include +#include +#include +#include + +#include "display_message.h" +#include "ufontlib.h" + +UFontManager *ufont_manager; + +NativeHandlerResult display_task_consume_mailbox(Context *ctx) +{ + struct DisplayTaskArgs *args = ctx->platform_data; + + MailboxMessage *mbox_msg = mailbox_take_message(&ctx->mailbox); + Message *msg = CONTAINER_OF(mbox_msg, Message, base); + + // Non-blocking enqueue; drop oldest on overflow. + if (xQueueSend(args->messages_queue, &msg, 0) != pdTRUE) { + + Message *old = NULL; + if (xQueueReceive(args->messages_queue, &old, 0) == pdTRUE && old) { + BEGIN_WITH_STACK_HEAP(1, temp_heap); + mailbox_message_dispose(&old->base, &temp_heap); + END_WITH_STACK_HEAP(temp_heap, ctx->global); + } + + if (xQueueSend(args->messages_queue, &msg, 0) != pdTRUE) { + BEGIN_WITH_STACK_HEAP(1, temp_heap2); + mailbox_message_dispose(&msg->base, &temp_heap2); + END_WITH_STACK_HEAP(temp_heap2, ctx->global); + } + } + + return NativeContinue; +} + +static bool try_handle_register_font(Message *message, Context *ctx) +{ + GenMessage gen_message; + if (UNLIKELY(port_parse_gen_message(message->message, + &gen_message) != GenCallMessage)) { + return false; + } + + term req = gen_message.req; + if (UNLIKELY(!term_is_tuple(req) || term_get_tuple_arity(req) < 1)) { + return false; + } + term cmd = term_get_tuple_element(req, 0); + + if (cmd != globalcontext_make_atom(ctx->global, + "\xD" "register_font")) { + return false; + } + + term font_bin = term_get_tuple_element(req, 2); + EpdFont *loaded_font = ufont_parse( + term_binary_data(font_bin), term_binary_size(font_bin)); + + char *handle = interop_atom_to_string(ctx, + term_get_tuple_element(req, 1)); + if (loaded_font != NULL && handle != NULL) { + ufont_manager_register(ufont_manager, handle, loaded_font); + } + free(handle); + + BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2) + REF_SIZE, heap); + term return_tuple = term_alloc_tuple(2, &heap); + term_put_tuple_element(return_tuple, 0, gen_message.ref); + term_put_tuple_element(return_tuple, 1, OK_ATOM); + display_message_send(gen_message.pid, return_tuple, ctx->global); + END_WITH_STACK_HEAP(heap, ctx->global); + + return true; +} + +void display_task_process_messages(void *arg) +{ + struct DisplayTaskArgs *args = arg; + + ufont_manager = ufont_manager_new(); + + while (true) { + Message *message; + xQueueReceive(args->messages_queue, &message, portMAX_DELAY); + + if (!try_handle_register_font(message, args->ctx)) { + args->process_message_fn(message, args->ctx); + } + + BEGIN_WITH_STACK_HEAP(1, temp_heap); + mailbox_message_dispose(&message->base, &temp_heap); + END_WITH_STACK_HEAP(temp_heap, args->ctx->global); + } +} diff --git a/display_task.h b/display_task.h new file mode 100644 index 0000000..1e1933f --- /dev/null +++ b/display_task.h @@ -0,0 +1,40 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _DISPLAY_TASK_H_ +#define _DISPLAY_TASK_H_ + +#include +#include + +#include +#include + +struct DisplayTaskArgs +{ + QueueHandle_t messages_queue; + void (*process_message_fn)(Message *message, Context *ctx); + Context *ctx; +}; + +NativeHandlerResult display_task_consume_mailbox(Context *ctx); +void display_task_process_messages(void *arg); + +#endif diff --git a/draw_common.h b/draw_common.h deleted file mode 100644 index c64d581..0000000 --- a/draw_common.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * This file is part of AtomGL. - * - * Copyright 2020-2022 Davide Bettio - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include - -static int draw_image_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item); -static int draw_scaled_cropped_img_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item); -static int draw_rect_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item); -static int draw_text_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item); - -static int find_max_line_len(BaseDisplayItem *items, int count, int xpos, int ypos) -{ - int line_len = DISPLAY_WIDTH - xpos; - - for (int i = 0; i < count; i++) { - BaseDisplayItem *item = &items[i]; - - if ((xpos < item->x) && (ypos >= item->y) && (ypos < item->y + item->height)) { - int len_to_item = item->x - xpos; - line_len = (line_len > len_to_item) ? len_to_item : line_len; - } - } - - return line_len; -} - -static int draw_x(uint8_t *line_buf, int xpos, int ypos, BaseDisplayItem *items, int items_count) -{ - bool below = false; - - for (int i = 0; i < items_count; i++) { - BaseDisplayItem *item = &items[i]; - if ((xpos < item->x) || (xpos >= item->x + item->width) || (ypos < item->y) || (ypos >= item->y + item->height)) { - continue; - } - - int max_line_len = below ? 1 : find_max_line_len(items, i, xpos, ypos); - - int drawn_pixels = 0; - switch (items[i].primitive) { - case Image: - //fprintf(stderr, "Image\n"); - drawn_pixels = draw_image_x(line_buf, xpos, ypos, max_line_len, item); - break; - - case ScaledCroppedImage: - //fprintf(stderr, "ScaledCroppedImage\n"); - drawn_pixels = draw_scaled_cropped_img_x(line_buf, xpos, ypos, max_line_len, item); - break; - - case Rect: - //fprintf(stderr, "Rect\n"); - drawn_pixels = draw_rect_x(line_buf, xpos, ypos, max_line_len, item); - break; - - case Text: - //fprintf(stderr, "Text\n"); - drawn_pixels = draw_text_x(line_buf, xpos, ypos, max_line_len, item); - break; - - default: { - fprintf(stderr, "unexpected display list command.\n"); - } - } - - if (drawn_pixels != 0) { - return drawn_pixels; - } - - below = true; - } - - return 1; -} diff --git a/epaper_color.c b/epaper_color.c new file mode 100644 index 0000000..ddd27b7 --- /dev/null +++ b/epaper_color.c @@ -0,0 +1,89 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2022-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "epaper_color.h" + +#include +#include +#include + +const uint8_t epaper_acep_palette[7][3] = { + { 0x00, 0x00, 0x00 }, + { 0xFF, 0xFF, 0xFF }, + { 0x00, 0xFF, 0x00 }, + { 0x00, 0x00, 0xFF }, + { 0xFF, 0x00, 0x00 }, + { 0xFF, 0xFF, 0x00 }, + { 0xFF, 0x80, 0x00 } +}; + +const uint8_t epaper_gdep073e01_palette[7][3] = { + { 0x19, 0x1E, 0x21 }, + { 0xE8, 0xE8, 0xE8 }, + { 0xEF, 0xDE, 0x44 }, + { 0xB2, 0x13, 0x18 }, + { 0xE8, 0xE8, 0xE8 }, + { 0x21, 0x57, 0xBA }, + { 0x12, 0x5F, 0x20 } +}; + +static inline float square(float p) +{ + return p * p; +} + +uint8_t epaper_dither_acep7(int x, int y, uint8_t r, uint8_t g, uint8_t b, + const uint8_t palette[][3], int palette_size) +{ + const uint8_t m[4][4] = { + { 0, 8, 2, 10 }, + { 12, 4, 14, 6 }, + { 3, 11, 1, 9 }, + { 15, 7, 13, 5 } + }; + + // following r parameters have been found using standard deviation + // that gives a decent result + int r1 = r + roundf(92.0 * ((float) m[x % 4][y % 4] * 0.0625 - 0.5)); + int g1 = g + roundf(85.0 * ((float) m[x % 4][y % 4] * 0.0625 - 0.5)); + int b1 = b + roundf(65.0 * ((float) m[x % 4][y % 4] * 0.0625 - 0.5)); + + float min = INT_MAX; + int min_index = 0; + + for (int i = 0; i < palette_size; i++) { + int r2 = palette[i][0]; + int g2 = palette[i][1]; + int b2 = palette[i][2]; + +#ifdef NO_WEIGHTS + float d = square((r2 - r1)) + square((g2 - g1)) + square((b2 - b1)); +#else + float d = square((r2 - r1) * 0.30) + square((g2 - g1) * 0.59) + square((b2 - b1) * 0.11); +#endif + + if (d < min) { + min = d; + min_index = i; + } + } + + return min_index; +} diff --git a/epaper_color.h b/epaper_color.h new file mode 100644 index 0000000..57c24a9 --- /dev/null +++ b/epaper_color.h @@ -0,0 +1,32 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2022-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _EPAPER_COLOR_H_ +#define _EPAPER_COLOR_H_ + +#include + +extern const uint8_t epaper_acep_palette[7][3]; +extern const uint8_t epaper_gdep073e01_palette[7][3]; + +uint8_t epaper_dither_acep7(int x, int y, uint8_t r, uint8_t g, uint8_t b, + const uint8_t palette[][3], int palette_size); + +#endif diff --git a/epaper_commands.c b/epaper_commands.c new file mode 100644 index 0000000..9a67aa2 --- /dev/null +++ b/epaper_commands.c @@ -0,0 +1,174 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "epaper_commands.h" + +#include +#include + +#include + +#include "epaper_color.h" + +static void wait_busy_high(int busy_gpio) +{ + while (gpio_get_level(busy_gpio) != 1) { + vTaskDelay(100); + } +} + +void epaper_execute_init_seq(struct SPIDCBus *bus, int busy_gpio, + const uint8_t *seq, size_t seq_len, bool wait_busy_between_cmds) +{ + const uint8_t *end = seq + seq_len; + while (seq < end) { + uint8_t cmd = *seq++; + uint8_t flags_len = *seq++; + uint8_t len = flags_len & 0x7F; + + spi_dc_write_cmd_data(bus, cmd, seq, len); + seq += len; + + if (flags_len & EPAPER_INIT_SEQ_DELAY) { + vTaskDelay(*seq++ / portTICK_PERIOD_MS); + } + + if (wait_busy_between_cmds) { + wait_busy_high(busy_gpio); + } + } +} + +// --- Built-in init sequences --- +// +// Transcribed mechanically from the original display_spi_init() +// bodies in 5in65_acep_7c_display_driver.c and +// gdep073e01_display_driver.c. _Static_assert on sizeof guards +// against miscounted data bytes. + +// clang-format off + +// Waveshare 5.65" ACeP 7-color. No BUSY polling between commands; a +// single 100 ms delay between VDCS (0x82) and the second CDI (0x50) +// replicates the vTaskDelay(10) at tick rate 100 Hz in the original +// driver. +const uint8_t epaper_init_seq_acep7c[] = { + 0x00, 2, 0xEF, 0x08, // PSR + 0x01, 4, 0x37, 0x00, 0x23, 0x23, // PWRR + 0x03, 1, 0x00, // POFS + 0x06, 3, 0xC7, 0xC7, 0x1D, // BTST + 0x30, 1, 0x3C, // PLL + 0x40, 1, 0x00, // TSE + 0x50, 1, 0x3F, // CDI + 0x60, 1, 0x22, // TCON + 0x61, 4, 0x02, 0x58, 0x01, 0xC0, // TRES (600x448) + 0xE3, 1, 0xAA, // PWS + 0x82, EPAPER_INIT_SEQ_DELAY | 1, 0x80, 100, // VDCS + 100 ms + 0x50, 1, 0x37, // CDI (second) +}; +_Static_assert(sizeof(epaper_init_seq_acep7c) == 46, + "epaper_init_seq_acep7c: miscounted bytes"); +const size_t epaper_init_seq_acep7c_len = sizeof(epaper_init_seq_acep7c); + +// Good Display GDEP073E01 7.3" 7-color. BUSY polling interleaved +// after each command by the executor (wait_busy_between_cmds = true); +// no per-command delay bytes are required. The leading 0xAA entry is +// an undocumented vendor preamble preserved verbatim. +const uint8_t epaper_init_seq_gdep073e01[] = { + 0xAA, 6, 0x49, 0x55, 0x20, 0x08, 0x09, 0x18, // vendor preamble + 0x01, 1, 0x3F, // PWRR + 0x00, 2, 0x5F, 0x69, // PSR + 0x03, 4, 0x00, 0x54, 0x00, 0x44, // POFS + 0x05, 4, 0x40, 0x1F, 0x1F, 0x2C, // BTST1 + 0x06, 4, 0x6F, 0x1F, 0x17, 0x49, // BTST2 + 0x08, 4, 0x6F, 0x1F, 0x1F, 0x22, // BTST3 + 0x30, 1, 0x00, // PLL + 0x50, 1, 0x3F, // CDI + 0x60, 2, 0x02, 0x00, // TCON + 0x61, 4, 0x03, 0x20, 0x01, 0xE0, // TRES (800x480) + 0x84, 1, 0x01, // T_VDCS + 0xE3, 1, 0x2F, // PWS + 0x04, 0, // PON +}; +_Static_assert(sizeof(epaper_init_seq_gdep073e01) == 63, + "epaper_init_seq_gdep073e01: miscounted bytes"); +const size_t epaper_init_seq_gdep073e01_len = sizeof(epaper_init_seq_gdep073e01); + +// --- Per-frame preambles --- +// +// ACeP 5.65" retransmits the resolution command (0x61 + 600x448) +// before every DTM; GoodDisplay's GDEP073E01 does not. The preamble +// uses the same byte-array format as init_seq. + +static const uint8_t epaper_preamble_acep7c[] = { + 0x61, 4, 0x02, 0x58, 0x01, 0xC0, // TRES (600x448) +}; +_Static_assert(sizeof(epaper_preamble_acep7c) == 6, + "epaper_preamble_acep7c: miscounted bytes"); + +// --- Per-panel descriptors --- + +const struct EPaperDesc epaper_desc_acep7c = { + .name = "Waveshare 5.65\" ACeP 7-color", + .native_width = 600, + .native_height = 448, + .spi_clock_hz = 1000000, + + .palette = epaper_acep_palette, + .palette_size = 7, + + .init_seq = epaper_init_seq_acep7c, + .init_seq_len = sizeof(epaper_init_seq_acep7c), + .init_wait_busy_between_cmds = false, + + .frame_preamble_seq = epaper_preamble_acep7c, + .frame_preamble_seq_len = sizeof(epaper_preamble_acep7c), + + .refresh_has_data = false, + .refresh_data_byte = 0x00, + .post_power_off_busy_level = 0, + + .periodic_refresh_interval = 5, +}; + +const struct EPaperDesc epaper_desc_gdep073e01 = { + .name = "Good Display GDEP073E01 7.3\" 7-color", + .native_width = 800, + .native_height = 480, + .spi_clock_hz = 4000000, + + .palette = epaper_gdep073e01_palette, + .palette_size = 7, + + .init_seq = epaper_init_seq_gdep073e01, + .init_seq_len = sizeof(epaper_init_seq_gdep073e01), + .init_wait_busy_between_cmds = true, + + .frame_preamble_seq = NULL, + .frame_preamble_seq_len = 0, + + .refresh_has_data = true, + .refresh_data_byte = 0x00, + .post_power_off_busy_level = 1, + + .periodic_refresh_interval = 0, +}; + +// clang-format on diff --git a/epaper_commands.h b/epaper_commands.h new file mode 100644 index 0000000..c90faf0 --- /dev/null +++ b/epaper_commands.h @@ -0,0 +1,109 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _EPAPER_COMMANDS_H_ +#define _EPAPER_COMMANDS_H_ + +#include +#include +#include + +#include "spi_dc_driver.h" + +// --- Init sequence byte-array format --- +// +// Each entry: [CMD] [FLAGS_LEN] [DATA_0 ... DATA_N] [DELAY_MS] +// CMD: command byte (any value, including 0x00) +// FLAGS_LEN: bits 6:0 = data byte count (0-127) +// bit 7 = delay flag (DELAY_MS byte follows data) +// DELAY_MS: delay in milliseconds (0-255), present only if flag set +// +// The sequence is length-bounded: callers pass the array length as +// seq_len and the executor walks the buffer until that count is +// exhausted. Length framing (rather than a sentinel byte) lets any +// command byte appear in an init sequence unambiguously — notably +// 0x00, which is PSR on most Waveshare / GoodDisplay controllers. + +#define EPAPER_INIT_SEQ_DELAY 0x80 + +// Execute an init sequence over an SPI+DC bus. seq/seq_len describe +// a byte array in the format documented above. When +// wait_busy_between_cmds is true, a busy-high poll is inserted after +// each command (mirroring the Good Display / Waveshare convention of +// "command completes when BUSY rises"). busy_gpio is ignored when +// the flag is false. +void epaper_execute_init_seq(struct SPIDCBus *bus, int busy_gpio, + const uint8_t *seq, size_t seq_len, bool wait_busy_between_cmds); + +// Built-in init sequences. Each array is paired with a size_t +// constant giving its length; callers pass both to +// epaper_execute_init_seq(). +extern const uint8_t epaper_init_seq_acep7c[]; +extern const size_t epaper_init_seq_acep7c_len; +extern const uint8_t epaper_init_seq_gdep073e01[]; +extern const size_t epaper_init_seq_gdep073e01_len; + +// --- Per-panel descriptor --- +// +// Captures every panel-specific knob so a single unified driver can +// drive multiple controllers by compatible-string dispatch. The +// struct carries no function pointers: the current variation across +// ACeP 5.65" and GDEP073E01 is entirely data. + +struct EPaperDesc +{ + const char *name; + int native_width; + int native_height; + int spi_clock_hz; + + // Color palette (RGB triplets) and its entry count. + const uint8_t (*palette)[3]; + int palette_size; + + // One-time init sequence (format documented above). + const uint8_t *init_seq; + size_t init_seq_len; + bool init_wait_busy_between_cmds; + + // Optional per-frame preamble sent before DTM (0x10) on every + // do_update and clear_screen. NULL when unused. Uses the same + // byte-array format as init_seq and is executed without inter- + // command BUSY polling. + const uint8_t *frame_preamble_seq; + size_t frame_preamble_seq_len; + + // Post-frame refresh protocol: PON (0x04) -> wait BUSY=1; DRF + // (0x12) optionally followed by one data byte, then wait BUSY=1; + // POF (0x02) then wait BUSY=post_power_off_busy_level. + bool refresh_has_data; + uint8_t refresh_data_byte; + int post_power_off_busy_level; + + // Periodic full-screen white-out. Zero = disabled. N > 0 means + // "call clear_screen(7) once every N do_update() invocations" to + // prevent ghosting on panels that need it (ACeP 5.65"). + int periodic_refresh_interval; +}; + +extern const struct EPaperDesc epaper_desc_acep7c; +extern const struct EPaperDesc epaper_desc_gdep073e01; + +#endif diff --git a/epaper_display_driver.c b/epaper_display_driver.c new file mode 100644 index 0000000..6b5b763 --- /dev/null +++ b/epaper_display_driver.c @@ -0,0 +1,460 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2022-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include "display_items.h" +#include "display_message.h" +#include "display_task.h" +#include "display_common.h" +#include "epaper_color.h" +#include "epaper_commands.h" +#include "epaper_draw.h" +#include "epaper_screen.h" +#include "image_helpers.h" +#include "spi_dc_driver.h" +#include "spi_display.h" + +#define REPORT_UNEXPECTED_MSGS 0 +#define SELF_TEST 0 + +static const char *TAG = "epaper_display_driver"; + +static void clear_screen(Context *ctx, int color); + +struct EpaperDriver +{ + struct SPIDCBus bus; + + int busy_gpio; + int reset_gpio; + + const struct EPaperDesc *desc; + struct EpaperScreen screen; + + Context *ctx; + + int count_to_refresh; + uint64_t last_refresh; + + struct DisplayTaskArgs display_args; +}; + +#define EPAPER_DRIVER_FROM_CTX(ctx) \ + CONTAINER_OF((struct DisplayTaskArgs *) (ctx)->platform_data, struct EpaperDriver, display_args) + +static const struct { + const char *compat; + const struct EPaperDesc *desc; +} epaper_compat_table[] = { + { "waveshare,5in65-acep-7c", &epaper_desc_acep7c }, + { "good-display/gdep073e01", &epaper_desc_gdep073e01 }, +}; + +static const struct EPaperDesc *epaper_desc_for_compatible(const char *compat) +{ + for (size_t i = 0; i < sizeof(epaper_compat_table) / sizeof(epaper_compat_table[0]); i++) { + if (!strcmp(compat, epaper_compat_table[i].compat)) { + return epaper_compat_table[i].desc; + } + } + return NULL; +} + +static void display_init_using_list(struct EpaperDriver *driver, term init_list); + +static void display_reset(struct EpaperDriver *driver) +{ + gpio_set_level(driver->reset_gpio, 0); + vTaskDelay(100); + gpio_set_level(driver->reset_gpio, 1); +} + +static void wait_busy_level(struct EpaperDriver *driver, int level) +{ + while (gpio_get_level(driver->busy_gpio) != level) { + vTaskDelay(100); + } +} + +static void wait_some_time(Context *ctx) +{ + struct EpaperDriver *driver = EPAPER_DRIVER_FROM_CTX(ctx); + + struct timeval tv; + gettimeofday(&tv, NULL); + uint64_t now = tv.tv_sec * 1000LL + (tv.tv_usec / 1000LL); + uint64_t delta = now - driver->last_refresh; + if (delta < 2000) { + // Wait 2 seconds before allowing a new refresh; undocumented but + // empirically required or the panel drops updates. + vTaskDelay((2000 - delta) / portTICK_PERIOD_MS); + } +} + +static void update_last_refresh_ts(Context *ctx) +{ + struct EpaperDriver *driver = EPAPER_DRIVER_FROM_CTX(ctx); + + struct timeval tv; + gettimeofday(&tv, NULL); + driver->last_refresh = tv.tv_sec * 1000LL + (tv.tv_usec / 1000LL); +} + +static void maybe_refresh(Context *ctx) +{ + struct EpaperDriver *driver = EPAPER_DRIVER_FROM_CTX(ctx); + if (driver->desc->periodic_refresh_interval <= 0) { + return; + } + + driver->count_to_refresh--; + if (driver->count_to_refresh <= 0) { + // 7 is the panel's white entry on both current palettes. + clear_screen(ctx, 7); + update_last_refresh_ts(ctx); + driver->count_to_refresh = driver->desc->periodic_refresh_interval; + } +} + +static void send_frame_preamble(struct EpaperDriver *driver) +{ + if (driver->desc->frame_preamble_seq != NULL) { + epaper_execute_init_seq(&driver->bus, driver->busy_gpio, + driver->desc->frame_preamble_seq, + driver->desc->frame_preamble_seq_len, false); + } +} + +static void send_post_frame_refresh(struct EpaperDriver *driver) +{ + // PON + spi_dc_write_command(&driver->bus, 0x04); + wait_busy_level(driver, 1); + + // DRF (+ optional data byte) + spi_dc_write_command(&driver->bus, 0x12); + if (driver->desc->refresh_has_data) { + spi_dc_write_data_n(&driver->bus, &driver->desc->refresh_data_byte, 1); + } + wait_busy_level(driver, 1); + + // POF + spi_dc_write_command(&driver->bus, 0x02); + wait_busy_level(driver, driver->desc->post_power_off_busy_level); +} + +static void do_update(Context *ctx, term display_list) +{ + maybe_refresh(ctx); + wait_some_time(ctx); + + int proper; + int len = term_list_length(display_list, &proper); + + BaseDisplayItem *items = malloc(sizeof(BaseDisplayItem) * len); + + term t = display_list; + for (int i = 0; i < len; i++) { + display_items_init_item(&items[i], term_get_list_head(t), ctx); + t = term_get_list_tail(t); + } + + struct EpaperDriver *driver = EPAPER_DRIVER_FROM_CTX(ctx); + int screen_width = driver->screen.w; + int screen_height = driver->screen.h; + + send_frame_preamble(driver); + + // DTM — data transfer to panel memory. + spi_dc_write_command(&driver->bus, 0x10); + + uint8_t *buf = heap_caps_malloc(screen_width / 2, MALLOC_CAP_DMA); + memset(buf, 0x11, screen_width / 2); + + bool transaction_in_progress = false; + + spi_device_acquire_bus(driver->bus.spi_disp.handle, portMAX_DELAY); + + for (int ypos = 0; ypos < screen_height; ypos++) { + if (transaction_in_progress) { + spi_transaction_t *trans = NULL; + spi_device_get_trans_result(driver->bus.spi_disp.handle, &trans, portMAX_DELAY); + } + + int xpos = 0; + while (xpos < screen_width) { + int drawn_pixels = epaper_draw_x(&driver->screen, buf, xpos, ypos, items, len); + xpos += drawn_pixels; + } + + spi_display_dma_write(&driver->bus.spi_disp, screen_width / 2, buf); + transaction_in_progress = true; + } + + if (transaction_in_progress) { + spi_transaction_t *trans = NULL; + spi_device_get_trans_result(driver->bus.spi_disp.handle, &trans, portMAX_DELAY); + } + + spi_device_release_bus(driver->bus.spi_disp.handle); + + free(buf); + + send_post_frame_refresh(driver); + + display_items_delete(items, len); + + update_last_refresh_ts(ctx); +} + +static void process_message(Message *message, Context *ctx) +{ + GenMessage gen_message; + if (UNLIKELY(port_parse_gen_message(message->message, &gen_message) != GenCallMessage)) { + fprintf(stderr, "Received invalid message."); + AVM_ABORT(); + } + + term req = gen_message.req; + if (UNLIKELY(!term_is_tuple(req) || term_get_tuple_arity(req) < 1)) { + AVM_ABORT(); + } + term cmd = term_get_tuple_element(req, 0); + + if (cmd == context_make_atom(ctx, "\x6" + "update")) { + + term display_list = term_get_tuple_element(req, 1); + do_update(ctx, display_list); + + } else if (cmd == globalcontext_make_atom(ctx->global, "\xA" "load_image")) { + handle_load_image(req, gen_message.ref, gen_message.pid, ctx); + return; + + } else { +#if REPORT_UNEXPECTED_MSGS + fprintf(stderr, "display: "); + term_display(stderr, req, ctx); + fprintf(stderr, "\n"); +#endif + } + + BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2) + REF_SIZE, heap); + term return_tuple = term_alloc_tuple(2, &heap); + term_put_tuple_element(return_tuple, 0, gen_message.ref); + term_put_tuple_element(return_tuple, 1, OK_ATOM); + + display_message_send(gen_message.pid, return_tuple, ctx->global); + END_WITH_STACK_HEAP(heap, ctx->global); +} + +static void clear_screen(Context *ctx, int color) +{ + struct EpaperDriver *driver = EPAPER_DRIVER_FROM_CTX(ctx); + int screen_width = driver->screen.w; + int screen_height = driver->screen.h; + + send_frame_preamble(driver); + + spi_dc_write_command(&driver->bus, 0x10); + + uint8_t *buf = heap_caps_malloc(screen_width / 2, MALLOC_CAP_DMA); + + bool transaction_in_progress = false; + + spi_device_acquire_bus(driver->bus.spi_disp.handle, portMAX_DELAY); + + for (int i = 0; i < screen_height; i++) { + if (transaction_in_progress) { + spi_transaction_t *trans = NULL; + spi_device_get_trans_result(driver->bus.spi_disp.handle, &trans, portMAX_DELAY); + } + + // memset inside the loop so every scanline carries fresh data, + // avoiding artefacts if a prior scanline left stale bytes. + memset(buf, color | (color << 4), screen_width / 2); + spi_display_dma_write(&driver->bus.spi_disp, screen_width / 2, buf); + transaction_in_progress = true; + } + + if (transaction_in_progress) { + spi_transaction_t *trans = NULL; + spi_device_get_trans_result(driver->bus.spi_disp.handle, &trans, portMAX_DELAY); + } + + spi_device_release_bus(driver->bus.spi_disp.handle); + + free(buf); + + send_post_frame_refresh(driver); +} + +static void display_spi_init(Context *ctx, term opts) +{ + // Resolve compatible string -> per-panel descriptor. + term compat_term = interop_kv_get_value_default( + opts, ATOM_STR("\xA", "compatible"), term_nil(), ctx->global); + int str_ok; + char *compat_string = interop_term_to_string(compat_term, &str_ok); + const struct EPaperDesc *desc = NULL; + if (str_ok && compat_string) { + desc = epaper_desc_for_compatible(compat_string); + } + if (!desc) { + ESP_LOGE(TAG, "Failed init: unknown or missing compatible '%s'.", + compat_string ? compat_string : "(null)"); + free(compat_string); + return; + } + free(compat_string); + + struct EpaperDriver *driver = malloc(sizeof(struct EpaperDriver)); + // TODO check here + + driver->desc = desc; + driver->ctx = ctx; + driver->screen.w = desc->native_width; + driver->screen.h = desc->native_height; + driver->screen.palette = desc->palette; + driver->screen.palette_size = desc->palette_size; + + driver->display_args.messages_queue = xQueueCreate(32, sizeof(Message *)); + driver->display_args.process_message_fn = process_message; + driver->display_args.ctx = ctx; + ctx->platform_data = &driver->display_args; + + struct SPIDisplayConfig spi_config; + spi_display_init_config(&spi_config); + spi_config.clock_speed_hz = desc->spi_clock_hz; + spi_display_parse_config(&spi_config, opts, ctx->global); + spi_display_init(&driver->bus.spi_disp, &spi_config); + + bool ok = display_common_gpio_from_opts(opts, ATOM_STR("\x4", "busy"), &driver->busy_gpio, ctx->global); + ok = ok && display_common_gpio_from_opts(opts, ATOM_STR("\x2", "dc"), &driver->bus.dc_gpio, ctx->global); + ok = ok && display_common_gpio_from_opts(opts, ATOM_STR("\x5", "reset"), &driver->reset_gpio, ctx->global); + if (UNLIKELY(!ok)) { + ESP_LOGE(TAG, "Failed init: invalid display GPIOs."); + return; + } + + gpio_set_direction(driver->reset_gpio, GPIO_MODE_OUTPUT); + gpio_set_level(driver->reset_gpio, 1); + gpio_set_direction(driver->bus.dc_gpio, GPIO_MODE_OUTPUT); + gpio_set_pull_mode(driver->bus.dc_gpio, GPIO_PULLUP_ENABLE); + gpio_set_direction(driver->busy_gpio, GPIO_MODE_INPUT); + gpio_set_pull_mode(driver->busy_gpio, GPIO_PULLUP_ENABLE); + gpio_set_level(driver->bus.dc_gpio, 0); + + display_reset(driver); + + wait_busy_level(driver, 1); + + // Init sequence: init_list opt overrides the descriptor default. + term init_list = interop_kv_get_value_default( + opts, ATOM_STR("\x9", "init_list"), term_nil(), ctx->global); + if (init_list != term_nil()) { + display_init_using_list(driver, init_list); + } else { + epaper_execute_init_seq(&driver->bus, driver->busy_gpio, + desc->init_seq, desc->init_seq_len, + desc->init_wait_busy_between_cmds); + } + + update_last_refresh_ts(ctx); + driver->count_to_refresh = 0; + +#if SELF_TEST + for (int i = 0; i < 8; i++) { + fprintf(stderr, "color: %i\n", i); + clear_screen(ctx, i); + vTaskDelay(30000 / portTICK_PERIOD_MS); + } + clear_screen(ctx, 1); + + while (1) + ; +#else + xTaskCreate(display_task_process_messages, "display", 10000, &driver->display_args, 1, NULL); +#endif +} + +Context *epaper_display_create_port(GlobalContext *global, term opts) +{ + Context *ctx = context_new(global); + ctx->native_handler = display_task_consume_mailbox; + display_spi_init(ctx, opts); + return ctx; +} + +// Erlang-side init override: accepts a list of +// {CmdByte :: 0..255, Binary :: binary()} +// {sleep_ms, Ms :: 0..255} +// {wait_busy_level, Level :: 0 | 1} +// tuples and applies them in order. Mirrors dcs_lcd's display_init_using_list +// with an added wait_busy_level clause, since e-paper controllers typically +// require BUSY-pin polling between commands that DCS LCDs do not. +static void display_init_using_list(struct EpaperDriver *driver, term init_list) +{ + term t = init_list; + while (term_is_nonempty_list(t)) { + term head = term_get_list_head(t); + if (term_is_tuple(head) && term_get_tuple_arity(head) == 2) { + term cmd_term = term_get_tuple_element(head, 0); + term data_term = term_get_tuple_element(head, 1); + if (term_is_integer(cmd_term) && term_is_binary(data_term)) { + avm_int_t cmd = term_to_int(cmd_term); + const uint8_t *data = (const uint8_t *) term_binary_data(data_term); + spi_dc_write_cmd_data(&driver->bus, cmd, data, term_binary_size(data_term)); + } else if ((cmd_term == context_make_atom(driver->ctx, ATOM_STR("\x8", "sleep_ms"))) + && term_is_integer(data_term)) { + vTaskDelay(term_to_int(data_term) / portTICK_PERIOD_MS); + } else if ((cmd_term == context_make_atom(driver->ctx, ATOM_STR("\xF", "wait_busy_level"))) + && term_is_integer(data_term)) { + wait_busy_level(driver, term_to_int(data_term)); + } else { + break; + } + } else { + break; + } + + t = term_get_list_tail(t); + } + if (t != term_nil()) { + fprintf(stderr, "Invalid init_list!\n"); + } +} diff --git a/epaper_draw.c b/epaper_draw.c new file mode 100644 index 0000000..e394097 --- /dev/null +++ b/epaper_draw.c @@ -0,0 +1,341 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2022-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "epaper_draw.h" + +#include +#include +#include + +#include + +#include "display_items.h" +#include "epaper_color.h" +#include "epaper_screen.h" +#include "font_data.h" + +void epaper_draw_pixel_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, uint8_t c) +{ + if (xpos > screen->w) { + fprintf(stderr, "buf ovf!\n"); + return; + } + + if ((xpos & 1) == 0) { + line_buf[xpos / 2] = (line_buf[xpos / 2] & 0xF) | (c << 4); + } else { + line_buf[xpos / 2] = (line_buf[xpos / 2] & 0xF0) | c; + } +} + +int epaper_find_max_line_len(const struct EpaperScreen *screen, + BaseDisplayItem items[], size_t items_len, int xpos, int ypos) +{ + int line_len = screen->w - xpos; + + for (size_t i = 0; i < items_len; i++) { + BaseDisplayItem *item = &items[i]; + + if ((xpos < item->x) && (ypos >= item->y) && (ypos < item->y + item->height)) { + int len_to_item = item->x - xpos; + line_len = (line_len > len_to_item) ? len_to_item : line_len; + } + } + + return line_len; +} + +int epaper_draw_image_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item) +{ + int x = item->x; + int y = item->y; + + int bgcolor_r; + int bgcolor_g; + int bgcolor_b; + bool visible_bg; + if (item->brcolor != 0) { + bgcolor_r = (item->brcolor >> 24) & 0xFF; + bgcolor_g = (item->brcolor >> 16) & 0xFF; + bgcolor_b = (item->brcolor >> 8) & 0xFF; + visible_bg = true; + } else { + bgcolor_r = 0; + bgcolor_g = 0; + bgcolor_b = 0; + visible_bg = false; + } + + int width = item->width; + const char *data = item->data.image_data.pix; + + int drawn_pixels = 0; + + uint32_t *pixels = ((uint32_t *) data) + (ypos - y) * width + (xpos - x); + + if (width > xpos - x + max_line_len) { + width = xpos - x + max_line_len; + } + + for (int j = xpos - x; j < width; j++) { + uint32_t img_pixel = READ_32_UNALIGNED(pixels); + if ((*pixels >> 24) & 0xFF) { + uint8_t r = img_pixel >> 24; + uint8_t g = (img_pixel >> 16) & 0xFF; + uint8_t b = (img_pixel >> 8) & 0xFF; + + uint8_t c = epaper_dither_acep7(xpos + drawn_pixels, ypos, r, g, b, + screen->palette, screen->palette_size); + epaper_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); + + } else if (visible_bg) { + uint8_t c = epaper_dither_acep7(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b, + screen->palette, screen->palette_size); + epaper_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); + + } else { + return drawn_pixels; + } + drawn_pixels++; + pixels++; + } + + return drawn_pixels; +} + +int epaper_draw_rect_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item) +{ + int x = item->x; + int width = item->width; + + uint8_t r = (item->brcolor >> 24) & 0xFF; + uint8_t g = (item->brcolor >> 16) & 0xFF; + uint8_t b = (item->brcolor >> 8) & 0xFF; + + int drawn_pixels = 0; + + if (width > xpos - x + max_line_len) { + width = xpos - x + max_line_len; + } + + for (int j = xpos - x; j < width; j++) { + uint8_t c = epaper_dither_acep7(xpos + drawn_pixels, ypos, r, g, b, + screen->palette, screen->palette_size); + epaper_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); + drawn_pixels++; + } + + return drawn_pixels; +} + +int epaper_draw_text_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item) +{ + int x = item->x; + int y = item->y; + bool visible_bg; + + int fgcolor_r = (item->data.text_data.fgcolor >> 24) & 0xFF; + int fgcolor_g = (item->data.text_data.fgcolor >> 16) & 0xFF; + int fgcolor_b = (item->data.text_data.fgcolor >> 8) & 0xFF; + + int bgcolor_r; + int bgcolor_g; + int bgcolor_b; + + if (item->brcolor != 0) { + bgcolor_r = (item->brcolor >> 24) & 0xFF; + bgcolor_g = (item->brcolor >> 16) & 0xFF; + bgcolor_b = (item->brcolor >> 8) & 0xFF; + visible_bg = true; + } else { + bgcolor_r = 0; + bgcolor_g = 0; + bgcolor_b = 0; + visible_bg = false; + } + + char *text = (char *) item->data.text_data.text; + + int width = item->width; + + int drawn_pixels = 0; + + if (width > xpos - x + max_line_len) { + width = xpos - x + max_line_len; + } + + for (int j = xpos - x; j < width; j++) { + int char_index = j / CHAR_WIDTH; + char c = text[char_index]; + unsigned const char *glyph = fontdata + ((unsigned char) c) * 16; + + unsigned char row = glyph[ypos - y]; + + bool opaque; + int k = j % CHAR_WIDTH; + if (row & (1 << (7 - k))) { + opaque = true; + } else { + opaque = false; + } + + if (opaque) { + uint8_t c = epaper_dither_acep7(xpos + drawn_pixels, ypos, fgcolor_r, fgcolor_g, fgcolor_b, + screen->palette, screen->palette_size); + epaper_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); + + } else if (visible_bg) { + uint8_t c = epaper_dither_acep7(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b, + screen->palette, screen->palette_size); + epaper_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); + + } else { + return drawn_pixels; + } + drawn_pixels++; + } + + return drawn_pixels; +} + +int epaper_draw_scaled_cropped_img_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item) +{ + int x = item->x; + int y = item->y; + + int bgcolor_r; + int bgcolor_g; + int bgcolor_b; + bool visible_bg; + if (item->brcolor != 0) { + bgcolor_r = (item->brcolor >> 24) & 0xFF; + bgcolor_g = (item->brcolor >> 16) & 0xFF; + bgcolor_b = (item->brcolor >> 8) & 0xFF; + visible_bg = true; + } else { + bgcolor_r = 0; + bgcolor_g = 0; + bgcolor_b = 0; + visible_bg = false; + } + + int width = item->width; + const char *data = item->data.image_data_with_size.pix; + + int drawn_pixels = 0; + + int y_scale = item->y_scale; + int x_scale = item->x_scale; + int img_width = item->data.image_data_with_size.width; + + int source_x = item->source_x; + int source_y = item->source_y; + + uint32_t *pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((xpos - x) / x_scale); + + if (source_x + (width / x_scale) > img_width) { + width = (img_width - source_x) * x_scale; + } + + if (width > xpos - x + max_line_len) { + width = xpos - x + max_line_len; + } + + for (int j = xpos - x; j < width; j++) { + uint32_t img_pixel = READ_32_UNALIGNED(pixels); + if ((*pixels >> 24) & 0xFF) { + uint8_t r = img_pixel >> 24; + uint8_t g = (img_pixel >> 16) & 0xFF; + uint8_t b = (img_pixel >> 8) & 0xFF; + + uint8_t c = epaper_dither_acep7(xpos + drawn_pixels, ypos, r, g, b, + screen->palette, screen->palette_size); + epaper_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); + + } else if (visible_bg) { + uint8_t c = epaper_dither_acep7(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b, + screen->palette, screen->palette_size); + epaper_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); + + } else { + return drawn_pixels; + } + drawn_pixels++; + pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((j + 1) / x_scale); + } + + return drawn_pixels; +} + +int epaper_draw_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, int ypos, + BaseDisplayItem items[], size_t items_len) +{ + bool below = false; + + for (size_t i = 0; i < items_len; i++) { + BaseDisplayItem *item = &items[i]; + if ((xpos < item->x) || (xpos >= item->x + item->width) || (ypos < item->y) || (ypos >= item->y + item->height)) { + continue; + } + + int max_line_len = below ? 1 : epaper_find_max_line_len(screen, items, i, xpos, ypos); + + int drawn_pixels = 0; + switch (items[i].primitive) { + case PrimitiveImage: + drawn_pixels = epaper_draw_image_x(screen, line_buf, xpos, ypos, max_line_len, item); + break; + + case PrimitiveScaledCroppedImage: + drawn_pixels = epaper_draw_scaled_cropped_img_x(screen, line_buf, xpos, ypos, max_line_len, item); + break; + + case PrimitiveRect: + drawn_pixels = epaper_draw_rect_x(screen, line_buf, xpos, ypos, max_line_len, item); + break; + + case PrimitiveText: + drawn_pixels = epaper_draw_text_x(screen, line_buf, xpos, ypos, max_line_len, item); + break; + + default: { + fprintf(stderr, "unexpected display list command.\n"); + } + } + + if (drawn_pixels != 0) { + return drawn_pixels; + } + + below = true; + } + + return 1; +} diff --git a/epaper_draw.h b/epaper_draw.h new file mode 100644 index 0000000..22df4d0 --- /dev/null +++ b/epaper_draw.h @@ -0,0 +1,53 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2022-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _EPAPER_DRAW_H_ +#define _EPAPER_DRAW_H_ + +#include "epaper_screen.h" +#include "display_items.h" + +void epaper_draw_pixel_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, uint8_t c); + +int epaper_draw_image_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item); + +int epaper_draw_rect_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item); + +int epaper_draw_text_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item); + +int epaper_draw_scaled_cropped_img_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item); + +int epaper_find_max_line_len(const struct EpaperScreen *screen, + BaseDisplayItem items[], size_t items_len, int xpos, int ypos); + +int epaper_draw_x(const struct EpaperScreen *screen, + uint8_t *line_buf, int xpos, int ypos, + BaseDisplayItem items[], size_t items_len); + +#endif diff --git a/epaper_screen.h b/epaper_screen.h new file mode 100644 index 0000000..8e8d87f --- /dev/null +++ b/epaper_screen.h @@ -0,0 +1,34 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _EPAPER_SCREEN_H_ +#define _EPAPER_SCREEN_H_ + +#include + +struct EpaperScreen +{ + int w; + int h; + const uint8_t (*palette)[3]; + int palette_size; +}; + +#endif diff --git a/font.c b/font_data.c similarity index 99% rename from font.c rename to font_data.c index 4e6d9f9..b72a491 100644 --- a/font.c +++ b/font_data.c @@ -2,9 +2,9 @@ // SPDX-FileCopyrightText: Linux kernel developers et al. // See also lib/fonts/font_8x16.c -#define FONTDATAMAX 4096 +#include "font_data.h" -static const unsigned char fontdata[FONTDATAMAX] = { +const unsigned char fontdata[FONTDATAMAX] = { /* */ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* */ 0x00,0x00,0x7e,0x81,0xa5,0x81,0x81,0xbd,0x99,0x81,0x81,0x7e,0x00,0x00,0x00,0x00, /* */ 0x00,0x00,0x7e,0xff,0xdb,0xff,0xff,0xc3,0xe7,0xff,0xff,0x7e,0x00,0x00,0x00,0x00, diff --git a/font_data.h b/font_data.h new file mode 100644 index 0000000..60f9b43 --- /dev/null +++ b/font_data.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-only +// SPDX-FileCopyrightText: Linux kernel developers et al. +// See also lib/fonts/font_8x16.c +// +// Style Exception (AVMCCS-N001/N005/N008): the identifiers FONTDATAMAX +// and fontdata are kept verbatim from the Linux kernel font_8x16 source +// to simplify comparison with upstream. Do not rename them. + +#ifndef _FONT_DATA_H_ +#define _FONT_DATA_H_ + +#define FONTDATAMAX 4096 +#define CHAR_WIDTH 8 + +extern const unsigned char fontdata[FONTDATAMAX]; + +#endif diff --git a/ili934x_display_driver.c b/ili934x_display_driver.c deleted file mode 100644 index f2e1d52..0000000 --- a/ili934x_display_driver.c +++ /dev/null @@ -1,919 +0,0 @@ -/* - * This file is part of AtomGL. - * - * Copyright 2020-2022 Davide Bettio - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "display_driver.h" - -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "backlight_gpio.h" -#include "display_common.h" -#include "display_items.h" -#include "image_helpers.h" -#include "spi_display.h" - -#define SPI_CLOCK_HZ 27000000 -#define SPI_MODE 0 - -#define CHAR_WIDTH 8 - -#define ILI9341_SLPIN 0x10 -#define ILI9341_SLPOUT 0x11 -#define ILI9341_PTLON 0x12 -#define ILI9341_NORON 0x13 - -#define ILI9341_INVOFF 0x20 -#define ILI9341_INVON 0x21 -#define ILI9341_GAMMASET 0x26 -#define ILI9341_DISPOFF 0x28 -#define ILI9341_DISPON 0x29 - -#define ILI9341_PTLAR 0x30 -#define ILI9341_VSCRDEF 0x33 -#define ILI9341_MADCTL 0x36 -#define ILI9341_VSCRSADD 0x37 -#define ILI9341_PIXFMT 0x3A - -#define ILI9341_FRMCTR1 0xB1 -#define ILI9341_FRMCTR2 0xB2 -#define ILI9341_FRMCTR3 0xB3 -#define ILI9341_INVCTR 0xB4 -#define ILI9341_DFUNCTR 0xB6 - -#define ILI9341_PWCTR1 0xC0 -#define ILI9341_PWCTR2 0xC1 -#define ILI9341_PWCTR3 0xC2 -#define ILI9341_PWCTR4 0xC3 -#define ILI9341_PWCTR5 0xC4 -#define ILI9341_VMCTR1 0xC5 -#define ILI9341_VMCTR2 0xC7 - -#define ILI9341_GMCTRP1 0xE0 -#define ILI9341_GMCTRN1 0xE1 - -#define TFT_SWRST 0x01 -#define TFT_CASET 0x2A -#define TFT_PASET 0x2B -#define TFT_RAMWR 0x2C - -#define TFT_MADCTL 0x36 -#define TFT_MAD_MY 0x80 -#define TFT_MAD_MX 0x40 -#define TFT_MAD_MV 0x20 -#define TFT_MAD_BGR 0x08 - -#define TFT_INVOFF 0x20 -#define TFT_INVON 0x21 - -#include "font.c" - -static const char *TAG = "ili934x_display_driver"; - -static void send_message(term pid, term message, GlobalContext *global); - -struct SPI -{ - struct SPIDisplay spi_disp; - int dc_gpio; - int reset_gpio; - - avm_int_t rotation; - - Context *ctx; -}; - -// This struct is just for compatibility reasons with the SDL display driver -// so it is possible to easily copy & paste code from there. -struct Screen -{ - int w; - int h; - uint16_t *pixels; - uint16_t *pixels_out; -}; - -static struct Screen *screen; - -// This functions is taken from: -// https://stackoverflow.com/questions/18937701/combining-two-16-bits-rgb-colors-with-alpha-blending -static inline uint16_t alpha_blend_rgb565(uint32_t fg, uint32_t bg, uint8_t alpha) -{ - alpha = (alpha + 4) >> 3; - bg = (bg | (bg << 16)) & 0b00000111111000001111100000011111; - fg = (fg | (fg << 16)) & 0b00000111111000001111100000011111; - uint32_t result = ((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111; - return (uint16_t)((result >> 16) | result); -} - -static inline uint8_t rgba8888_get_alpha(uint32_t color) -{ - return color & 0xFF; -} - -static inline uint16_t rgba8888_color_to_rgb565(struct Screen *s, uint32_t color) -{ - uint8_t r = color >> 24; - uint8_t g = (color >> 16) & 0xFF; - uint8_t b = (color >> 8) & 0xFF; - - return (((uint16_t)(r >> 3)) << 11) | (((uint16_t)(g >> 2)) << 5) | ((uint16_t) b >> 3); -} - -static inline uint16_t rgb565_color_to_surface(struct Screen *s, uint16_t color16) -{ - return (uint16_t) SPI_SWAP_DATA_TX(color16, 16); -} - -static inline uint16_t uint32_color_to_surface(struct Screen *s, uint32_t color) -{ - uint16_t color16 = rgba8888_color_to_rgb565(s, color); - - return rgb565_color_to_surface(s, color16); -} - -struct PendingReply -{ - uint64_t pending_call_ref_ticks; - term pending_call_pid; -}; - -static QueueHandle_t display_messages_queue; - -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx); -static void display_init(Context *ctx, term opts); -static void display_init42c(struct SPI *spi); -static void display_init41(struct SPI *spi); - -static inline void writedata(struct SPI *spi, uint32_t data) -{ - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - spi_display_write(&spi->spi_disp, 8, data); - spi_device_release_bus(spi->spi_disp.handle); -} - -static inline void writecommand(struct SPI *spi, uint8_t command) -{ - gpio_set_level(spi->dc_gpio, 0); - writedata(spi, command); - gpio_set_level(spi->dc_gpio, 1); -} - -static inline void set_screen_paint_area(struct SPI *spi, int x, int y, int width, int height) -{ - writecommand(spi, TFT_CASET); - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - spi_display_write(&spi->spi_disp, 32, (x << 16) | ((x + width) - 1)); - spi_device_release_bus(spi->spi_disp.handle); - - writecommand(spi, TFT_PASET); - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - spi_display_write(&spi->spi_disp, 32, (y << 16) | ((y + height) - 1)); - spi_device_release_bus(spi->spi_disp.handle); -} - -static int draw_image_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - - uint16_t bgcolor = 0; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor = rgba8888_color_to_rgb565(screen, item->brcolor); - visible_bg = true; - } else { - visible_bg = false; - } - - int width = item->width; - const char *data = item->data.image_data.pix; - - int drawn_pixels = 0; - - uint32_t *pixels = ((uint32_t *) data) + (ypos - y) * width + (xpos - x); - uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - uint32_t img_pixel = READ_32_UNALIGNED(pixels); - uint8_t alpha = rgba8888_get_alpha(img_pixel); - if (alpha == 0xFF) { - uint16_t color = uint32_color_to_surface(screen, img_pixel); - pixmem16[drawn_pixels] = color; - } else if (visible_bg) { - uint16_t color = rgba8888_color_to_rgb565(screen, img_pixel); - uint16_t blended = alpha_blend_rgb565(color, bgcolor, alpha); - pixmem16[drawn_pixels] = rgb565_color_to_surface(screen, blended); - } else { - return drawn_pixels; - } - drawn_pixels++; - pixels++; - } - - return drawn_pixels; -} - -static int draw_scaled_cropped_img_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - - uint16_t bgcolor = 0; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor = rgba8888_color_to_rgb565(screen, item->brcolor); - visible_bg = true; - } else { - visible_bg = false; - } - - int width = item->width; - const char *data = item->data.image_data_with_size.pix; - - int drawn_pixels = 0; - - int y_scale = item->y_scale; - int x_scale = item->x_scale; - int img_width = item->data.image_data_with_size.width; - - int source_x = item->source_x; - int source_y = item->source_y; - - uint32_t *pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((xpos - x) / x_scale); - uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (source_x + (width / x_scale) > img_width) { - width = (img_width - source_x) * x_scale; - } - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - uint32_t img_pixel = READ_32_UNALIGNED(pixels); - uint8_t alpha = rgba8888_get_alpha(img_pixel); - if (alpha == 0xFF) { - uint16_t color = uint32_color_to_surface(screen, img_pixel); - pixmem16[drawn_pixels] = color; - } else if (visible_bg) { - uint16_t color = rgba8888_color_to_rgb565(screen, img_pixel); - uint16_t blended = alpha_blend_rgb565(color, bgcolor, alpha); - pixmem16[drawn_pixels] = rgb565_color_to_surface(screen, blended); - } else { - return drawn_pixels; - } - drawn_pixels++; - // TODO: optimize here - pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + (j / x_scale); - } - - return drawn_pixels; -} - -static int draw_rect_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int width = item->width; - uint16_t color = uint32_color_to_surface(screen, item->brcolor); - - int drawn_pixels = 0; - - uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - pixmem16[drawn_pixels] = color; - drawn_pixels++; - } - - return drawn_pixels; -} - -static int draw_text_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - uint16_t fgcolor = uint32_color_to_surface(screen, item->data.text_data.fgcolor); - uint16_t bgcolor; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor = uint32_color_to_surface(screen, item->brcolor); - visible_bg = true; - } else { - visible_bg = false; - } - - char *text = (char *) item->data.text_data.text; - - int width = item->width; - - int drawn_pixels = 0; - - uint16_t *pixmem32 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - int char_index = j / CHAR_WIDTH; - char c = text[char_index]; - unsigned const char *glyph = fontdata + ((unsigned char) c) * 16; - - unsigned char row = glyph[ypos - y]; - - bool opaque; - int k = j % CHAR_WIDTH; - if (row & (1 << (7 - k))) { - opaque = true; - } else { - opaque = false; - } - - if (opaque) { - pixmem32[drawn_pixels] = fgcolor; - } else if (visible_bg) { - pixmem32[drawn_pixels] = bgcolor; - } else { - return drawn_pixels; - } - drawn_pixels++; - } - - return drawn_pixels; -} - -static int find_max_line_len(BaseDisplayItem *items, int count, int xpos, int ypos) -{ - int line_len = screen->w - xpos; - - for (int i = 0; i < count; i++) { - BaseDisplayItem *item = &items[i]; - - if ((xpos < item->x) && (ypos >= item->y) && (ypos < item->y + item->height)) { - int len_to_item = item->x - xpos; - line_len = (line_len > len_to_item) ? len_to_item : line_len; - } - } - - return line_len; -} - -static int draw_x(int xpos, int ypos, BaseDisplayItem *items, int items_count) -{ - bool below = false; - - for (int i = 0; i < items_count; i++) { - BaseDisplayItem *item = &items[i]; - if ((xpos < item->x) || (xpos >= item->x + item->width) || (ypos < item->y) || (ypos >= item->y + item->height)) { - continue; - } - - int max_line_len = below ? 1 : find_max_line_len(items, i, xpos, ypos); - - int drawn_pixels = 0; - switch (items[i].primitive) { - case Image: - drawn_pixels = draw_image_x(xpos, ypos, max_line_len, item); - break; - - case Rect: - drawn_pixels = draw_rect_x(xpos, ypos, max_line_len, item); - break; - - case ScaledCroppedImage: - drawn_pixels = draw_scaled_cropped_img_x(xpos, ypos, max_line_len, item); - break; - - case Text: - drawn_pixels = draw_text_x(xpos, ypos, max_line_len, item); - break; - default: { - fprintf(stderr, "unexpected display list command.\n"); - } - } - - if (drawn_pixels != 0) { - return drawn_pixels; - } - - below = true; - } - - return 1; -} - -static void do_update(Context *ctx, term display_list) -{ - int proper; - int len = term_list_length(display_list, &proper); - - BaseDisplayItem *items = malloc(sizeof(BaseDisplayItem) * len); - - term t = display_list; - for (int i = 0; i < len; i++) { - init_item(&items[i], term_get_list_head(t), ctx); - t = term_get_list_tail(t); - } - - int screen_width = screen->w; - int screen_height = screen->h; - struct SPI *spi = ctx->platform_data; - - set_screen_paint_area(spi, 0, 0, screen_width, screen_height); - writecommand(spi, TFT_RAMWR); - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - - bool transaction_in_progress = false; - - for (int ypos = 0; ypos < screen_height; ypos++) { - int xpos = 0; - while (xpos < screen_width) { - int drawn_pixels = draw_x(xpos, ypos, items, len); - xpos += drawn_pixels; - } - - if (transaction_in_progress) { - spi_transaction_t *trans; - // I did a quick measurement, and most of the time is spent waiting for DMA transaction - // eg. 23 us spent in draw_x, 188 us spent in spi_device_get_trans_result - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); - } - - //NEW CODE - void *tmp = screen->pixels; - screen->pixels = screen->pixels_out; - screen->pixels_out = tmp; - spi_display_dmawrite(&spi->spi_disp, screen_width * sizeof(uint16_t), screen->pixels_out); - transaction_in_progress = true; - } - - if (transaction_in_progress) { - spi_transaction_t *trans; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); - } - - spi_device_release_bus(spi->spi_disp.handle); - - destroy_items(items, len); -} - -void draw_buffer(struct SPI *spi, int x, int y, int width, int height, const void *imgdata) -{ - const uint16_t *data = imgdata; - - set_screen_paint_area(spi, x, y, width, height); - - writecommand(spi, TFT_RAMWR); - - int dest_size = width * height; - int buf_pixel_size = (dest_size > 1024) ? 1024 : dest_size; - - int chunks = dest_size / 1024; - - uint16_t *tmpbuf = heap_caps_malloc(buf_pixel_size * sizeof(uint16_t), MALLOC_CAP_DMA); - - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - for (int i = 0; i < chunks; i++) { - const uint16_t *data_b = data + 1024 * i; - for (int j = 0; j < 1024; j++) { - tmpbuf[j] = SPI_SWAP_DATA_TX(data_b[j], 16); - } - spi_display_dmawrite(&spi->spi_disp, buf_pixel_size * sizeof(uint16_t), tmpbuf); - } - int last_chunk_size = dest_size - chunks * 1024; - if (last_chunk_size) { - const uint16_t *data_b = data + chunks * 1024; - for (int j = 0; j < 1024; j++) { - tmpbuf[j] = SPI_SWAP_DATA_TX(data_b[j], 16); - } - spi_display_dmawrite(&spi->spi_disp, last_chunk_size * sizeof(uint16_t), tmpbuf); - } - spi_device_release_bus(spi->spi_disp.handle); - - free(tmpbuf); -} - -static void process_message(Message *message, Context *ctx) -{ - GenMessage gen_message; - if (UNLIKELY(port_parse_gen_message(message->message, &gen_message) != GenCallMessage)) { - fprintf(stderr, "Received invalid message."); - AVM_ABORT(); - } - - term req = gen_message.req; - if (UNLIKELY(!term_is_tuple(req) || term_get_tuple_arity(req) < 1)) { - AVM_ABORT(); - } - term cmd = term_get_tuple_element(req, 0); - - struct SPI *spi = ctx->platform_data; - - if (cmd == context_make_atom(ctx, "\x6" - "update")) { - term display_list = term_get_tuple_element(req, 1); - do_update(ctx, display_list); - - } else if (cmd == context_make_atom(ctx, "\xB" - "draw_buffer")) { - int x = term_to_int(term_get_tuple_element(req, 1)); - int y = term_to_int(term_get_tuple_element(req, 2)); - int width = term_to_int(term_get_tuple_element(req, 3)); - int height = term_to_int(term_get_tuple_element(req, 4)); - unsigned long addr_low = term_to_int(term_get_tuple_element(req, 5)); - unsigned long addr_high = term_to_int(term_get_tuple_element(req, 6)); - - const void *data = (const void *) ((addr_low | (addr_high << 16))); - - draw_buffer(spi, x, y, width, height, data); - - // draw_buffer is a kind of cast, no need to reply - return; - - } else if (cmd == globalcontext_make_atom(ctx->global, "\xA" "load_image")) { - handle_load_image(req, gen_message.ref, gen_message.pid, ctx); - return; - - } else { - fprintf(stderr, "display: "); - term_display(stderr, req, ctx); - fprintf(stderr, "\n"); - } - - BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2) + REF_SIZE, heap); - term return_tuple = term_alloc_tuple(2, &heap); - term_put_tuple_element(return_tuple, 0, gen_message.ref); - term_put_tuple_element(return_tuple, 1, OK_ATOM); - - send_message(gen_message.pid, return_tuple, ctx->global); - END_WITH_STACK_HEAP(heap, ctx->global); -} - -static void process_messages(void *arg) -{ - struct SPI *args = arg; - - while (true) { - Message *message; - xQueueReceive(display_messages_queue, &message, portMAX_DELAY); - process_message(message, args->ctx); - - BEGIN_WITH_STACK_HEAP(1, temp_heap); - mailbox_message_dispose(&message->base, &temp_heap); - END_WITH_STACK_HEAP(temp_heap, args->ctx->global); - } -} - -void display_enqueue_message(Message *message) -{ - xQueueSend(display_messages_queue, &message, 1); -} - -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx) -{ - MailboxMessage *mbox_msg = mailbox_take_message(&ctx->mailbox); - Message *msg = CONTAINER_OF(mbox_msg, Message, base); - - xQueueSend(display_messages_queue, &msg, 1); - - return NativeContinue; -} - -static void set_rotation(struct SPI *spi, int rotation) -{ - if (rotation == 1) { - writecommand(spi, TFT_MADCTL); - writedata(spi, TFT_MAD_BGR | TFT_MAD_MY | TFT_MAD_MV); - } -} - -Context *ili934x_display_create_port(GlobalContext *global, term opts) -{ - Context *ctx = context_new(global); - ctx->native_handler = display_driver_consume_mailbox; - display_init(ctx, opts); - return ctx; -} - -static void send_message(term pid, term message, GlobalContext *global) -{ - int local_process_id = term_to_local_process_id(pid); - globalcontext_send_message(global, local_process_id, message); -} - -static void display_init(Context *ctx, term opts) -{ - screen = malloc(sizeof(struct Screen)); - // FIXME: hardcoded width and height - screen->w = 320; - screen->h = 240; - screen->pixels = heap_caps_malloc(screen->w * sizeof(uint16_t), MALLOC_CAP_DMA); - screen->pixels_out = heap_caps_malloc(screen->w * sizeof(uint16_t), MALLOC_CAP_DMA); - - display_messages_queue = xQueueCreate(32, sizeof(Message *)); - - struct SPI *spi = malloc(sizeof(struct SPI)); - ctx->platform_data = spi; - - spi->ctx = ctx; - - struct SPIDisplayConfig spi_config; - spi_display_init_config(&spi_config); - spi_config.mode = SPI_MODE; - spi_config.clock_speed_hz = SPI_CLOCK_HZ; - spi_display_parse_config(&spi_config, opts, ctx->global); - spi_display_init(&spi->spi_disp, &spi_config); - - bool ok = display_common_gpio_from_opts(opts, ATOM_STR("\x2", "dc"), &spi->dc_gpio, ctx->global); - ok = ok && display_common_gpio_from_opts(opts, ATOM_STR("\x5", "reset"), &spi->reset_gpio, ctx->global); - - term compat_value_term = interop_kv_get_value_default(opts, ATOM_STR("\xA", "compatible"), term_nil(), ctx->global); - int str_ok; - char *compat_string = interop_term_to_string(compat_value_term, &str_ok); - bool enable_ili93442c = false; - if (str_ok && compat_string) { - enable_ili93442c = !strcmp(compat_string, "ilitek,ili9342c"); - free(compat_string); - } else { - ok = false; - } - - term rotation = interop_kv_get_value_default(opts, ATOM_STR("\x8", "rotation"), term_from_int(0), ctx->global); - ok = ok && term_is_integer(rotation); - spi->rotation = term_to_int(rotation); - - term invon = interop_kv_get_value_default(opts, ATOM_STR("\x10", "enable_tft_invon"), FALSE_ATOM, ctx->global); - ok = ok && ((invon == TRUE_ATOM) || (invon == FALSE_ATOM)); - bool enable_tft_invon = (invon == TRUE_ATOM); - - if (UNLIKELY(!ok)) { - ESP_LOGE(TAG, "Failed init: invalid display parameters."); - return; - } - - // Reset - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - gpio_set_direction(spi->reset_gpio, GPIO_MODE_OUTPUT); - gpio_set_level(spi->reset_gpio, 1); - vTaskDelay(50 / portTICK_PERIOD_MS); - gpio_set_level(spi->reset_gpio, 0); - vTaskDelay(50 / portTICK_PERIOD_MS); - gpio_set_level(spi->reset_gpio, 1); - spi_device_release_bus(spi->spi_disp.handle); - - gpio_set_direction(spi->dc_gpio, GPIO_MODE_OUTPUT); - - writecommand(spi, TFT_SWRST); - - vTaskDelay(5 / portTICK_PERIOD_MS); - - if (enable_ili93442c) { - display_init42c(spi); - } else { - display_init41(spi); - } - - writecommand(spi, ILI9341_SLPOUT); - - vTaskDelay(120 / portTICK_PERIOD_MS); - - writecommand(spi, ILI9341_DISPON); - - if (enable_tft_invon) { - writecommand(spi, TFT_INVON); - } - - set_rotation(spi, spi->rotation); - - struct BacklightGPIOConfig backlight_config; - backlight_gpio_init_config(&backlight_config); - backlight_gpio_parse_config(&backlight_config, opts, ctx->global); - backlight_gpio_init(&backlight_config); - - xTaskCreate(process_messages, "display", 10000, spi, 1, NULL); -} - -static void display_init41(struct SPI *spi) -{ - writecommand(spi, 0xEF); - writedata(spi, 0x03); - writedata(spi, 0x80); - writedata(spi, 0x02); - - writecommand(spi, 0xCF); - writedata(spi, 0x00); - writedata(spi, 0xC1); - writedata(spi, 0x30); - - writecommand(spi, 0xED); - writedata(spi, 0x64); - writedata(spi, 0x03); - writedata(spi, 0x12); - writedata(spi, 0x81); - - writecommand(spi, 0xE8); - writedata(spi, 0x85); - writedata(spi, 0x00); - writedata(spi, 0x78); - - writecommand(spi, 0xCB); - writedata(spi, 0x39); - writedata(spi, 0x2C); - writedata(spi, 0x00); - writedata(spi, 0x34); - writedata(spi, 0x02); - - writecommand(spi, 0xF7); - writedata(spi, 0x20); - - writecommand(spi, 0xEA); - writedata(spi, 0x00); - writedata(spi, 0x00); - - writecommand(spi, ILI9341_PWCTR1); - writedata(spi, 0x23); - - writecommand(spi, ILI9341_PWCTR2); - writedata(spi, 0x10); - - writecommand(spi, ILI9341_VMCTR1); - writedata(spi, 0x3E); - writedata(spi, 0x28); - - writecommand(spi, ILI9341_VMCTR2); - writedata(spi, 0x86); - - writecommand(spi, ILI9341_MADCTL); - writedata(spi, 0x08); - - writecommand(spi, ILI9341_PIXFMT); - writedata(spi, 0x55); - - writecommand(spi, ILI9341_FRMCTR1); - writedata(spi, 0x00); - writedata(spi, 0x13); - - writecommand(spi, ILI9341_DFUNCTR); - writedata(spi, 0x0A); - writedata(spi, 0xA2); - writedata(spi, 0x27); - - writecommand(spi, 0xF2); - writedata(spi, 0x00); - - writecommand(spi, ILI9341_GAMMASET); - writedata(spi, 0x01); - - writecommand(spi, ILI9341_GMCTRP1); - writedata(spi, 0x0F); - writedata(spi, 0x31); - writedata(spi, 0x2B); - writedata(spi, 0x0C); - writedata(spi, 0x0E); - writedata(spi, 0x08); - writedata(spi, 0x4E); - writedata(spi, 0xF1); - writedata(spi, 0x37); - writedata(spi, 0x07); - writedata(spi, 0x10); - writedata(spi, 0x03); - writedata(spi, 0x0E); - writedata(spi, 0x09); - writedata(spi, 0x00); - - writecommand(spi, ILI9341_GMCTRN1); - writedata(spi, 0x00); - writedata(spi, 0x0E); - writedata(spi, 0x14); - writedata(spi, 0x03); - writedata(spi, 0x11); - writedata(spi, 0x07); - writedata(spi, 0x31); - writedata(spi, 0xC1); - writedata(spi, 0x48); - writedata(spi, 0x08); - writedata(spi, 0x0F); - writedata(spi, 0x0C); - writedata(spi, 0x31); - writedata(spi, 0x36); - writedata(spi, 0x0F); -} - -static void display_init42c(struct SPI *spi) -{ - writecommand(spi, 0xC8); - writedata(spi, 0xFF); - writedata(spi, 0x93); - writedata(spi, 0x42); - - writecommand(spi, ILI9341_PWCTR1); - writedata(spi, 0x12); - writedata(spi, 0x12); - - writecommand(spi, ILI9341_PWCTR2); - writedata(spi, 0x03); - - writecommand(spi, 0xB0); - writedata(spi, 0xE0); - - writecommand(spi, 0xF6); - writedata(spi, 0x00); - writedata(spi, 0x01); - writedata(spi, 0x01); - - writecommand(spi, ILI9341_MADCTL); - writedata(spi, TFT_MAD_MY | TFT_MAD_MV); - - writecommand(spi, ILI9341_PIXFMT); - writedata(spi, 0x55); - - writecommand(spi, ILI9341_DFUNCTR); - writedata(spi, 0x08); - writedata(spi, 0x82); - writedata(spi, 0x27); - - writecommand(spi, ILI9341_GMCTRP1); - writedata(spi, 0x00); - writedata(spi, 0x0C); - writedata(spi, 0x11); - writedata(spi, 0x04); - writedata(spi, 0x11); - writedata(spi, 0x08); - writedata(spi, 0x37); - writedata(spi, 0x89); - writedata(spi, 0x4C); - writedata(spi, 0x06); - writedata(spi, 0x0C); - writedata(spi, 0x0A); - writedata(spi, 0x2E); - writedata(spi, 0x34); - writedata(spi, 0x0F); - - writecommand(spi, ILI9341_GMCTRN1); - writedata(spi, 0x00); - writedata(spi, 0x0B); - writedata(spi, 0x11); - writedata(spi, 0x05); - writedata(spi, 0x13); - writedata(spi, 0x09); - writedata(spi, 0x33); - writedata(spi, 0x67); - writedata(spi, 0x48); - writedata(spi, 0x07); - writedata(spi, 0x0E); - writedata(spi, 0x0B); - writedata(spi, 0x2E); - writedata(spi, 0x33); - writedata(spi, 0x0F); -} diff --git a/ili948x_display_driver.c b/ili948x_display_driver.c deleted file mode 100644 index ee15d76..0000000 --- a/ili948x_display_driver.c +++ /dev/null @@ -1,1043 +0,0 @@ -/* - * This file is part of AtomGL. - * - * Copyright 2020-2022 Davide Bettio - * Copyright 2025 Masatoshi Nishiguchi - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/* Based on ili934x_display_driver.c. */ - -#include "display_driver.h" - -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "backlight_gpio.h" -#include "display_common.h" -#include "display_items.h" -#include "image_helpers.h" -#include "spi_display.h" - -#define SPI_CLOCK_HZ 27000000 -#define SPI_MODE 0 - -#define CHAR_WIDTH 8 - -#define ILI948X_SWRESET 0x01 -#define ILI948X_SLPIN 0x10 -#define ILI948X_SLPOUT 0x11 -#define ILI948X_DISPOFF 0x28 -#define ILI948X_DISPON 0x29 - -#define ILI948X_CASET 0x2A -#define ILI948X_PASET 0x2B -#define ILI948X_RAMWR 0x2C - -#define ILI948X_MADCTL 0x36 -#define ILI948X_MAD_MY 0x80 -#define ILI948X_MAD_MX 0x40 -#define ILI948X_MAD_MV 0x20 -#define ILI948X_MAD_BGR 0x08 - -#define ILI948X_INVOFF 0x20 -#define ILI948X_INVON 0x21 - -#define ILI948X_PIXFMT 0x3A - -#define ILI948X_IFMODE 0xB0 -#define ILI948X_FRMCTR1 0xB1 -#define ILI948X_INVCTR 0xB4 -#define ILI948X_DFUNCTR 0xB6 -#define ILI948X_ETMOD 0xB7 -#define ILI948X_PWRCTR1 0xC0 -#define ILI948X_PWRCTR2 0xC1 -#define ILI948X_PWRCTR3 0xC2 -#define ILI948X_VMCTR1 0xC5 -#define ILI948X_HS_LANES_CTRL 0xBE -#define ILI948X_IMAGE_FUNCTION 0xE9 -#define ILI948X_PGAMCTRL 0xE0 -#define ILI948X_NGAMCTRL 0xE1 -#define ILI948X_DGAMCTRL 0xE2 -#define ILI948X_ADJCTRL3 0xF7 - -#define ILI948X_TFTWIDTH 320 -#define ILI948X_TFTHEIGHT 480 - -#define UNUSED(x) ((void) (x)) - -#include "font.c" - -static const char *TAG = "ili948x_display_driver"; - -static void send_message(term pid, term message, GlobalContext *global); - -struct SPI -{ - struct SPIDisplay spi_disp; - int dc_gpio; - int reset_gpio; - - avm_int_t rotation; - bool is_ili9488; - - bool madctl_bgr; - - Context *ctx; -}; - -// Double-buffered scanline buffers. -struct Screen -{ - int w; - int h; - uint16_t *pixels; - uint16_t *pixels_out; - - // ILI9488: 3 bytes/pixel. - uint8_t *bytes; - uint8_t *bytes_out; -}; - -static struct Screen *screen; - -// Alpha blending for RGB565. -static inline uint16_t alpha_blend_rgb565(uint32_t fg, uint32_t bg, uint8_t alpha) -{ - alpha = (alpha + 4) >> 3; - bg = (bg | (bg << 16)) & 0b00000111111000001111100000011111; - fg = (fg | (fg << 16)) & 0b00000111111000001111100000011111; - uint32_t result = ((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111; - return (uint16_t) ((result >> 16) | result); -} - -static inline uint8_t rgba8888_get_alpha(uint32_t color) -{ - return color & 0xFF; -} - -static inline uint16_t rgba8888_color_to_rgb565(struct Screen *s, uint32_t color) -{ - UNUSED(s); - - uint8_t r = color >> 24; - uint8_t g = (color >> 16) & 0xFF; - uint8_t b = (color >> 8) & 0xFF; - - return (((uint16_t) (r >> 3)) << 11) | (((uint16_t) (g >> 2)) << 5) | ((uint16_t) b >> 3); -} - -static inline uint16_t rgb565_color_to_surface(struct Screen *s, uint16_t color16) -{ - UNUSED(s); - - return (uint16_t) SPI_SWAP_DATA_TX(color16, 16); -} - -static inline uint16_t uint32_color_to_surface(struct Screen *s, uint32_t color) -{ - uint16_t color16 = rgba8888_color_to_rgb565(s, color); - - return rgb565_color_to_surface(s, color16); -} - -// ILI9488 scanline conversion: RGB565 -> RGB888 bytes. -static inline void rgb565swapped_line_to_rgb888(uint8_t *dst, const uint16_t *src_swapped, int n_pixels) -{ - for (int i = 0; i < n_pixels; i++) { - uint16_t px = (uint16_t) SPI_SWAP_DATA_TX(src_swapped[i], 16); - - uint8_t r5 = (px >> 11) & 0x1F; - uint8_t g6 = (px >> 5) & 0x3F; - uint8_t b5 = (px >> 0) & 0x1F; - - uint8_t r8 = (r5 << 3) | (r5 >> 2); - uint8_t g8 = (g6 << 2) | (g6 >> 4); - uint8_t b8 = (b5 << 3) | (b5 >> 2); - - dst[i * 3 + 0] = r8; - dst[i * 3 + 1] = g8; - dst[i * 3 + 2] = b8; - } -} - -struct PendingReply -{ - uint64_t pending_call_ref_ticks; - term pending_call_pid; -}; - -static QueueHandle_t display_messages_queue; - -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx); -static void display_init(Context *ctx, term opts); - -static void display_init9486(struct SPI *spi); -static void display_init9488(struct SPI *spi); - -static inline void writedata(struct SPI *spi, uint32_t data) -{ - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - spi_display_write(&spi->spi_disp, 8, data); - spi_device_release_bus(spi->spi_disp.handle); -} - -static inline void writecommand(struct SPI *spi, uint8_t command) -{ - gpio_set_level(spi->dc_gpio, 0); - writedata(spi, command); - gpio_set_level(spi->dc_gpio, 1); -} - -static inline void set_screen_paint_area(struct SPI *spi, int x, int y, int width, int height) -{ - writecommand(spi, ILI948X_CASET); - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - spi_display_write(&spi->spi_disp, 32, (x << 16) | ((x + width) - 1)); - spi_device_release_bus(spi->spi_disp.handle); - - writecommand(spi, ILI948X_PASET); - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - spi_display_write(&spi->spi_disp, 32, (y << 16) | ((y + height) - 1)); - spi_device_release_bus(spi->spi_disp.handle); -} - -static int draw_image_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - - uint16_t bgcolor = 0; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor = rgba8888_color_to_rgb565(screen, item->brcolor); - visible_bg = true; - } else { - visible_bg = false; - } - - int width = item->width; - const char *data = item->data.image_data.pix; - - int drawn_pixels = 0; - - uint32_t *pixels = ((uint32_t *) data) + (ypos - y) * width + (xpos - x); - uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - uint32_t img_pixel = READ_32_UNALIGNED(pixels); - uint8_t alpha = rgba8888_get_alpha(img_pixel); - if (alpha == 0xFF) { - uint16_t color = uint32_color_to_surface(screen, img_pixel); - pixmem16[drawn_pixels] = color; - } else if (visible_bg) { - uint16_t color = rgba8888_color_to_rgb565(screen, img_pixel); - uint16_t blended = alpha_blend_rgb565(color, bgcolor, alpha); - pixmem16[drawn_pixels] = rgb565_color_to_surface(screen, blended); - } else { - return drawn_pixels; - } - drawn_pixels++; - pixels++; - } - - return drawn_pixels; -} - -static int draw_scaled_cropped_img_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - - uint16_t bgcolor = 0; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor = rgba8888_color_to_rgb565(screen, item->brcolor); - visible_bg = true; - } else { - visible_bg = false; - } - - int width = item->width; - const char *data = item->data.image_data_with_size.pix; - - int drawn_pixels = 0; - - int y_scale = item->y_scale; - int x_scale = item->x_scale; - int img_width = item->data.image_data_with_size.width; - - int source_x = item->source_x; - int source_y = item->source_y; - - uint32_t *pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((xpos - x) / x_scale); - uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (source_x + (width / x_scale) > img_width) { - width = (img_width - source_x) * x_scale; - } - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - uint32_t img_pixel = READ_32_UNALIGNED(pixels); - uint8_t alpha = rgba8888_get_alpha(img_pixel); - if (alpha == 0xFF) { - uint16_t color = uint32_color_to_surface(screen, img_pixel); - pixmem16[drawn_pixels] = color; - } else if (visible_bg) { - uint16_t color = rgba8888_color_to_rgb565(screen, img_pixel); - uint16_t blended = alpha_blend_rgb565(color, bgcolor, alpha); - pixmem16[drawn_pixels] = rgb565_color_to_surface(screen, blended); - } else { - return drawn_pixels; - } - drawn_pixels++; - pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + (j / x_scale); - } - - return drawn_pixels; -} - -static int draw_rect_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int width = item->width; - uint16_t color = uint32_color_to_surface(screen, item->brcolor); - - int drawn_pixels = 0; - - uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - pixmem16[drawn_pixels] = color; - drawn_pixels++; - } - - return drawn_pixels; -} - -static int draw_text_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - uint16_t fgcolor = uint32_color_to_surface(screen, item->data.text_data.fgcolor); - uint16_t bgcolor; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor = uint32_color_to_surface(screen, item->brcolor); - visible_bg = true; - } else { - visible_bg = false; - } - - char *text = (char *) item->data.text_data.text; - - int width = item->width; - - int drawn_pixels = 0; - - uint16_t *pixmem32 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - int char_index = j / CHAR_WIDTH; - char c = text[char_index]; - unsigned const char *glyph = fontdata + ((unsigned char) c) * 16; - - unsigned char row = glyph[ypos - y]; - - bool opaque; - int k = j % CHAR_WIDTH; - if (row & (1 << (7 - k))) { - opaque = true; - } else { - opaque = false; - } - - if (opaque) { - pixmem32[drawn_pixels] = fgcolor; - } else if (visible_bg) { - pixmem32[drawn_pixels] = bgcolor; - } else { - return drawn_pixels; - } - drawn_pixels++; - } - - return drawn_pixels; -} - -static int find_max_line_len(BaseDisplayItem *items, int count, int xpos, int ypos) -{ - int line_len = screen->w - xpos; - - for (int i = 0; i < count; i++) { - BaseDisplayItem *item = &items[i]; - - if ((xpos < item->x) && (ypos >= item->y) && (ypos < item->y + item->height)) { - int len_to_item = item->x - xpos; - line_len = (line_len > len_to_item) ? len_to_item : line_len; - } - } - - return line_len; -} - -static int draw_x(int xpos, int ypos, BaseDisplayItem *items, int items_count) -{ - bool below = false; - - for (int i = 0; i < items_count; i++) { - BaseDisplayItem *item = &items[i]; - if ((xpos < item->x) || (xpos >= item->x + item->width) || (ypos < item->y) || (ypos >= item->y + item->height)) { - continue; - } - - int max_line_len = below ? 1 : find_max_line_len(items, i, xpos, ypos); - - int drawn_pixels = 0; - switch (items[i].primitive) { - case Image: - drawn_pixels = draw_image_x(xpos, ypos, max_line_len, item); - break; - - case Rect: - drawn_pixels = draw_rect_x(xpos, ypos, max_line_len, item); - break; - - case ScaledCroppedImage: - drawn_pixels = draw_scaled_cropped_img_x(xpos, ypos, max_line_len, item); - break; - - case Text: - drawn_pixels = draw_text_x(xpos, ypos, max_line_len, item); - break; - default: - fprintf(stderr, "unexpected display list command.\n"); - break; - } - - if (drawn_pixels != 0) { - return drawn_pixels; - } - - below = true; - } - - return 1; -} - -static void do_update(Context *ctx, term display_list) -{ - int proper; - int len = term_list_length(display_list, &proper); - - BaseDisplayItem *items = malloc(sizeof(BaseDisplayItem) * len); - - term t = display_list; - for (int i = 0; i < len; i++) { - init_item(&items[i], term_get_list_head(t), ctx); - t = term_get_list_tail(t); - } - - int screen_width = screen->w; - int screen_height = screen->h; - struct SPI *spi = ctx->platform_data; - - set_screen_paint_area(spi, 0, 0, screen_width, screen_height); - writecommand(spi, ILI948X_RAMWR); - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - - bool transaction_in_progress = false; - - for (int ypos = 0; ypos < screen_height; ypos++) { - int xpos = 0; - while (xpos < screen_width) { - int drawn_pixels = draw_x(xpos, ypos, items, len); - xpos += drawn_pixels; - } - - if (transaction_in_progress) { - spi_transaction_t *trans; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); - } - - // Swap scanline buffers. - void *tmp = screen->pixels; - screen->pixels = screen->pixels_out; - screen->pixels_out = tmp; - - if (!spi->is_ili9488) { - spi_display_dmawrite(&spi->spi_disp, screen_width * sizeof(uint16_t), screen->pixels_out); - } else { - void *tmpb = screen->bytes; - screen->bytes = screen->bytes_out; - screen->bytes_out = tmpb; - - rgb565swapped_line_to_rgb888(screen->bytes_out, screen->pixels_out, screen_width); - spi_display_dmawrite(&spi->spi_disp, screen_width * 3, screen->bytes_out); - } - - transaction_in_progress = true; - } - - if (transaction_in_progress) { - spi_transaction_t *trans; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); - } - - spi_device_release_bus(spi->spi_disp.handle); - - destroy_items(items, len); -} - -static void draw_buffer(struct SPI *spi, int x, int y, int width, int height, const void *imgdata) -{ - const uint16_t *data = imgdata; - - set_screen_paint_area(spi, x, y, width, height); - - writecommand(spi, ILI948X_RAMWR); - - int dest_size = width * height; - int chunks = dest_size / 1024; - - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - - if (!spi->is_ili9488) { - int buf_pixel_size = (dest_size > 1024) ? 1024 : dest_size; - uint16_t *tmpbuf = heap_caps_malloc(buf_pixel_size * sizeof(uint16_t), MALLOC_CAP_DMA); - - for (int i = 0; i < chunks; i++) { - const uint16_t *data_b = data + 1024 * i; - for (int j = 0; j < 1024; j++) { - tmpbuf[j] = SPI_SWAP_DATA_TX(data_b[j], 16); - } - spi_display_dmawrite(&spi->spi_disp, 1024 * sizeof(uint16_t), tmpbuf); - } - - int last_chunk_size = dest_size - chunks * 1024; - if (last_chunk_size) { - const uint16_t *data_b = data + chunks * 1024; - for (int j = 0; j < last_chunk_size; j++) { - tmpbuf[j] = SPI_SWAP_DATA_TX(data_b[j], 16); - } - spi_display_dmawrite(&spi->spi_disp, last_chunk_size * sizeof(uint16_t), tmpbuf); - } - - free(tmpbuf); - - } else { - // ILI9488: RGB565 -> RGB888 (3 bytes/pixel). - const int chunk_pixels = 512; - uint8_t *tmpbuf = heap_caps_malloc(chunk_pixels * 3, MALLOC_CAP_DMA); - - int i = 0; - while (i < dest_size) { - int n = (dest_size - i > chunk_pixels) ? chunk_pixels : (dest_size - i); - - for (int j = 0; j < n; j++) { - uint16_t px = data[i + j]; - uint8_t r5 = (px >> 11) & 0x1F; - uint8_t g6 = (px >> 5) & 0x3F; - uint8_t b5 = (px >> 0) & 0x1F; - - uint8_t r8 = (r5 << 3) | (r5 >> 2); - uint8_t g8 = (g6 << 2) | (g6 >> 4); - uint8_t b8 = (b5 << 3) | (b5 >> 2); - - tmpbuf[j * 3 + 0] = r8; - tmpbuf[j * 3 + 1] = g8; - tmpbuf[j * 3 + 2] = b8; - } - - spi_display_dmawrite(&spi->spi_disp, n * 3, tmpbuf); - i += n; - } - - free(tmpbuf); - } - - spi_device_release_bus(spi->spi_disp.handle); -} - -static void process_message(Message *message, Context *ctx) -{ - GenMessage gen_message; - if (UNLIKELY(port_parse_gen_message(message->message, &gen_message) != GenCallMessage)) { - fprintf(stderr, "Received invalid message."); - AVM_ABORT(); - } - - term req = gen_message.req; - if (UNLIKELY(!term_is_tuple(req) || term_get_tuple_arity(req) < 1)) { - AVM_ABORT(); - } - term cmd = term_get_tuple_element(req, 0); - - struct SPI *spi = ctx->platform_data; - - if (cmd == context_make_atom(ctx, "\x6" - "update")) { - term display_list = term_get_tuple_element(req, 1); - do_update(ctx, display_list); - - } else if (cmd == context_make_atom(ctx, "\xB" - "draw_buffer")) { - int x = term_to_int(term_get_tuple_element(req, 1)); - int y = term_to_int(term_get_tuple_element(req, 2)); - int width = term_to_int(term_get_tuple_element(req, 3)); - int height = term_to_int(term_get_tuple_element(req, 4)); - unsigned long addr_low = term_to_int(term_get_tuple_element(req, 5)); - unsigned long addr_high = term_to_int(term_get_tuple_element(req, 6)); - - const void *data = (const void *) ((addr_low | (addr_high << 16))); - - draw_buffer(spi, x, y, width, height, data); - - // draw_buffer is fire-and-forget. - return; - - } else if (cmd == globalcontext_make_atom(ctx->global, "\xA" - "load_image")) { - handle_load_image(req, gen_message.ref, gen_message.pid, ctx); - return; - - } else { - fprintf(stderr, "display: "); - term_display(stderr, req, ctx); - fprintf(stderr, "\n"); - } - - BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2) + REF_SIZE, heap); - term return_tuple = term_alloc_tuple(2, &heap); - term_put_tuple_element(return_tuple, 0, gen_message.ref); - term_put_tuple_element(return_tuple, 1, OK_ATOM); - - send_message(gen_message.pid, return_tuple, ctx->global); - END_WITH_STACK_HEAP(heap, ctx->global); -} - -static void process_messages(void *arg) -{ - struct SPI *args = arg; - - while (true) { - Message *message; - xQueueReceive(display_messages_queue, &message, portMAX_DELAY); - process_message(message, args->ctx); - - BEGIN_WITH_STACK_HEAP(1, temp_heap); - mailbox_message_dispose(&message->base, &temp_heap); - END_WITH_STACK_HEAP(temp_heap, args->ctx->global); - } -} - -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx) -{ - MailboxMessage *mbox_msg = mailbox_take_message(&ctx->mailbox); - Message *msg = CONTAINER_OF(mbox_msg, Message, base); - - // Non-blocking enqueue; drop oldest on overflow. - if (xQueueSend(display_messages_queue, &msg, 0) != pdTRUE) { - - Message *old = NULL; - if (xQueueReceive(display_messages_queue, &old, 0) == pdTRUE && old) { - BEGIN_WITH_STACK_HEAP(1, temp_heap); - mailbox_message_dispose(&old->base, &temp_heap); - END_WITH_STACK_HEAP(temp_heap, ctx->global); - } - - if (xQueueSend(display_messages_queue, &msg, 0) != pdTRUE) { - BEGIN_WITH_STACK_HEAP(1, temp_heap2); - mailbox_message_dispose(&msg->base, &temp_heap2); - END_WITH_STACK_HEAP(temp_heap2, ctx->global); - } - } - - return NativeContinue; -} - -static void set_rotation(struct SPI *spi, int rotation) -{ - uint8_t madctl = 0; - - if (spi->madctl_bgr) { - madctl |= ILI948X_MAD_BGR; - } - - switch (rotation & 3) { - case 0: - madctl |= ILI948X_MAD_MX; - break; - - case 1: - madctl |= ILI948X_MAD_MV; - break; - - case 2: - madctl |= ILI948X_MAD_MY; - break; - - case 3: - madctl |= ILI948X_MAD_MX | ILI948X_MAD_MY | ILI948X_MAD_MV; - break; - } - - writecommand(spi, ILI948X_MADCTL); - writedata(spi, madctl); -} - -Context *ili948x_display_create_port(GlobalContext *global, term opts) -{ - Context *ctx = context_new(global); - ctx->native_handler = display_driver_consume_mailbox; - display_init(ctx, opts); - return ctx; -} - -static void send_message(term pid, term message, GlobalContext *global) -{ - int local_process_id = term_to_local_process_id(pid); - globalcontext_send_message(global, local_process_id, message); -} - -static void display_init(Context *ctx, term opts) -{ - screen = malloc(sizeof(struct Screen)); - - display_messages_queue = xQueueCreate(32, sizeof(Message *)); - - struct SPI *spi = malloc(sizeof(struct SPI)); - ctx->platform_data = spi; - - spi->ctx = ctx; - - struct SPIDisplayConfig spi_config; - spi_display_init_config(&spi_config); - spi_config.mode = SPI_MODE; - spi_config.clock_speed_hz = SPI_CLOCK_HZ; - spi_display_parse_config(&spi_config, opts, ctx->global); - spi_display_init(&spi->spi_disp, &spi_config); - - bool ok = display_common_gpio_from_opts(opts, ATOM_STR("\x2", "dc"), &spi->dc_gpio, ctx->global); - ok = ok && display_common_gpio_from_opts(opts, ATOM_STR("\x5", "reset"), &spi->reset_gpio, ctx->global); - - term compat_value_term = interop_kv_get_value_default(opts, ATOM_STR("\xA", "compatible"), term_nil(), ctx->global); - int str_ok; - char *compat_string = interop_term_to_string(compat_value_term, &str_ok); - - bool is_ili9486 = false; - bool is_ili9488 = false; - if (str_ok && compat_string) { - is_ili9486 = !strcmp(compat_string, "ilitek,ili9486"); - is_ili9488 = !strcmp(compat_string, "ilitek,ili9488"); - free(compat_string); - } else { - ok = false; - } - - if (!is_ili9486 && !is_ili9488) { - ok = false; - } - spi->is_ili9488 = is_ili9488; - - // color_order: rgb|bgr (default: bgr) - term color_order_term = interop_kv_get_value_default(opts, ATOM_STR("\xB", "color_order"), term_nil(), ctx->global); - - if (term_is_nil(color_order_term)) { - spi->madctl_bgr = true; - } else if (term_is_atom(color_order_term)) { - if (color_order_term == context_make_atom(ctx, "\x3" - "rgb")) { - spi->madctl_bgr = false; - } else if (color_order_term == context_make_atom(ctx, "\x3" - "bgr")) { - spi->madctl_bgr = true; - } else { - ok = false; - } - } else { - ok = false; - } - - term rotation = interop_kv_get_value_default(opts, ATOM_STR("\x8", "rotation"), term_from_int(0), ctx->global); - ok = ok && term_is_integer(rotation); - spi->rotation = term_to_int(rotation); - - term invon = interop_kv_get_value_default(opts, ATOM_STR("\x10", "enable_tft_invon"), FALSE_ATOM, ctx->global); - ok = ok && ((invon == TRUE_ATOM) || (invon == FALSE_ATOM)); - bool enable_tft_invon = (invon == TRUE_ATOM); - - if (UNLIKELY(!ok)) { - ESP_LOGE(TAG, "Failed init: invalid display parameters."); - return; - } - - // Swap w/h for 90/270. - if (spi->rotation & 1) { - screen->w = ILI948X_TFTHEIGHT; - screen->h = ILI948X_TFTWIDTH; - } else { - screen->w = ILI948X_TFTWIDTH; - screen->h = ILI948X_TFTHEIGHT; - } - - screen->pixels = heap_caps_malloc(screen->w * sizeof(uint16_t), MALLOC_CAP_DMA); - screen->pixels_out = heap_caps_malloc(screen->w * sizeof(uint16_t), MALLOC_CAP_DMA); - - if (spi->is_ili9488) { - screen->bytes = heap_caps_malloc(screen->w * 3, MALLOC_CAP_DMA); - screen->bytes_out = heap_caps_malloc(screen->w * 3, MALLOC_CAP_DMA); - } else { - screen->bytes = NULL; - screen->bytes_out = NULL; - } - - // Reset. - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - gpio_set_direction(spi->reset_gpio, GPIO_MODE_OUTPUT); - gpio_set_level(spi->reset_gpio, 1); - vTaskDelay(50 / portTICK_PERIOD_MS); - gpio_set_level(spi->reset_gpio, 0); - vTaskDelay(50 / portTICK_PERIOD_MS); - gpio_set_level(spi->reset_gpio, 1); - spi_device_release_bus(spi->spi_disp.handle); - - gpio_set_direction(spi->dc_gpio, GPIO_MODE_OUTPUT); - - writecommand(spi, ILI948X_SWRESET); - - vTaskDelay(5 / portTICK_PERIOD_MS); - - if (spi->is_ili9488) { - display_init9488(spi); - } else { - display_init9486(spi); - } - - writecommand(spi, ILI948X_SLPOUT); - - vTaskDelay(120 / portTICK_PERIOD_MS); - - writecommand(spi, ILI948X_DISPON); - - if (enable_tft_invon) { - writecommand(spi, ILI948X_INVON); - } else { - writecommand(spi, ILI948X_INVOFF); - } - - set_rotation(spi, spi->rotation); - - struct BacklightGPIOConfig backlight_config; - backlight_gpio_init_config(&backlight_config); - backlight_gpio_parse_config(&backlight_config, opts, ctx->global); - backlight_gpio_init(&backlight_config); - - xTaskCreate(process_messages, "display", 10000, spi, 1, NULL); -} - -static void display_init9486(struct SPI *spi) -{ - writecommand(spi, ILI948X_IFMODE); - writedata(spi, 0x00); - - writecommand(spi, ILI948X_PIXFMT); - writedata(spi, 0x55); - - writecommand(spi, ILI948X_PWRCTR3); - writedata(spi, 0x44); - - writecommand(spi, ILI948X_VMCTR1); - writedata(spi, 0x00); - writedata(spi, 0x00); - writedata(spi, 0x00); - writedata(spi, 0x00); - - writecommand(spi, ILI948X_PGAMCTRL); - writedata(spi, 0x0f); - writedata(spi, 0x1f); - writedata(spi, 0x1c); - writedata(spi, 0x0c); - writedata(spi, 0x0f); - writedata(spi, 0x08); - writedata(spi, 0x48); - writedata(spi, 0x98); - writedata(spi, 0x37); - writedata(spi, 0x0a); - writedata(spi, 0x13); - writedata(spi, 0x04); - writedata(spi, 0x11); - writedata(spi, 0x0d); - writedata(spi, 0x00); - - writecommand(spi, ILI948X_NGAMCTRL); - writedata(spi, 0x0f); - writedata(spi, 0x32); - writedata(spi, 0x2e); - writedata(spi, 0x0b); - writedata(spi, 0x0d); - writedata(spi, 0x05); - writedata(spi, 0x47); - writedata(spi, 0x75); - writedata(spi, 0x37); - writedata(spi, 0x06); - writedata(spi, 0x10); - writedata(spi, 0x03); - writedata(spi, 0x24); - writedata(spi, 0x20); - writedata(spi, 0x00); - - writecommand(spi, ILI948X_DGAMCTRL); - writedata(spi, 0x0f); - writedata(spi, 0x32); - writedata(spi, 0x2e); - writedata(spi, 0x0b); - writedata(spi, 0x0d); - writedata(spi, 0x05); - writedata(spi, 0x47); - writedata(spi, 0x75); - writedata(spi, 0x37); - writedata(spi, 0x06); - writedata(spi, 0x10); - writedata(spi, 0x03); - writedata(spi, 0x24); - writedata(spi, 0x20); - writedata(spi, 0x00); -} - -static void display_init9488(struct SPI *spi) -{ - // ILI9488: RGB666 over SPI (3 bytes/pixel). - writecommand(spi, ILI948X_IFMODE); - writedata(spi, 0x00); - - writecommand(spi, ILI948X_ADJCTRL3); - writedata(spi, 0xA9); - writedata(spi, 0x51); - writedata(spi, 0x2C); - writedata(spi, 0x82); - - writecommand(spi, ILI948X_PWRCTR1); - writedata(spi, 0x11); - writedata(spi, 0x09); - - writecommand(spi, ILI948X_PWRCTR2); - writedata(spi, 0x41); - - writecommand(spi, ILI948X_VMCTR1); - writedata(spi, 0x00); - writedata(spi, 0x0A); - writedata(spi, 0x80); - - writecommand(spi, ILI948X_FRMCTR1); - writedata(spi, 0xB0); - writedata(spi, 0x11); - - writecommand(spi, ILI948X_INVCTR); - writedata(spi, 0x02); - - writecommand(spi, ILI948X_DFUNCTR); - writedata(spi, 0x02); - writedata(spi, 0x02); - - writecommand(spi, ILI948X_ETMOD); - writedata(spi, 0xC6); - - writecommand(spi, ILI948X_HS_LANES_CTRL); - writedata(spi, 0x00); - writedata(spi, 0x04); - - writecommand(spi, ILI948X_IMAGE_FUNCTION); - writedata(spi, 0x00); - - writecommand(spi, ILI948X_PIXFMT); - writedata(spi, 0x66); - - writecommand(spi, ILI948X_PGAMCTRL); - writedata(spi, 0x00); - writedata(spi, 0x07); - writedata(spi, 0x10); - writedata(spi, 0x09); - writedata(spi, 0x17); - writedata(spi, 0x0B); - writedata(spi, 0x41); - writedata(spi, 0x89); - writedata(spi, 0x4B); - writedata(spi, 0x0A); - writedata(spi, 0x0C); - writedata(spi, 0x0E); - writedata(spi, 0x18); - writedata(spi, 0x1B); - writedata(spi, 0x0F); - - writecommand(spi, ILI948X_NGAMCTRL); - writedata(spi, 0x00); - writedata(spi, 0x17); - writedata(spi, 0x1A); - writedata(spi, 0x04); - writedata(spi, 0x0E); - writedata(spi, 0x06); - writedata(spi, 0x2F); - writedata(spi, 0x45); - writedata(spi, 0x43); - writedata(spi, 0x02); - writedata(spi, 0x0A); - writedata(spi, 0x09); - writedata(spi, 0x32); - writedata(spi, 0x36); - writedata(spi, 0x0F); -} diff --git a/memory_display_driver.c b/memory_display_driver.c index 3b77279..0688ed4 100644 --- a/memory_display_driver.c +++ b/memory_display_driver.c @@ -51,62 +51,44 @@ #include #include "display_common.h" +#include "display_task.h" +#include "mono_draw.h" #include "spi_display.h" -#define CHAR_WIDTH 8 - #define DISPLAY_WIDTH 400 +#define DISPLAY_HEIGHT 240 -#define CHECK_OVERFLOW 1 #define REPORT_UNEXPECTED_MSGS 0 -#include "font.c" +#include "font_data.h" -struct SPI +struct MemoryLCDDriver { struct SPIDisplay spi_disp; Context *ctx; -}; -#include "display_items.h" -#include "draw_common.h" -#include "image_helpers.h" -#include "monochrome.h" + struct MonoScreen screen; -// This struct is just for compatibility reasons with the SDL display driver -// so it is possible to easily copy & paste code from there. -struct Screen -{ - int w; - int h; uint8_t *pixels; uint8_t *dma_out; - // keep double buffer disabled for now: uint16_t *pixels_out; -}; - -static struct Screen *screen; + int vcom; -struct PendingReply -{ - uint64_t pending_call_ref_ticks; - term pending_call_pid; + struct DisplayTaskArgs display_args; }; -static QueueHandle_t display_messages_queue; +#define MEMORY_LCD_DRIVER_FROM_CTX(ctx) \ + CONTAINER_OF((struct DisplayTaskArgs *) (ctx)->platform_data, struct MemoryLCDDriver, display_args) + +#include "display_items.h" +#include "display_message.h" +#include "image_helpers.h" -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx); static void display_init(Context *ctx, term opts); -int vcom = 0x0; -static inline int get_vcom() +static inline int next_vcom(struct MemoryLCDDriver *driver) { - int current_vcom = vcom; - if (!vcom) { - vcom = 0x2; - } else { - vcom = 0; - } - + int current_vcom = driver->vcom; + driver->vcom = current_vcom ? 0 : 0x2; return current_vcom; } @@ -119,52 +101,52 @@ static void do_update(Context *ctx, term display_list) term t = display_list; for (int i = 0; i < len; i++) { - init_item(&items[i], term_get_list_head(t), ctx); + display_items_init_item(&items[i], term_get_list_head(t), ctx); t = term_get_list_tail(t); } - int screen_width = screen->w; - int screen_height = screen->h; - struct SPI *spi = ctx->platform_data; + struct MemoryLCDDriver *driver = MEMORY_LCD_DRIVER_FROM_CTX(ctx); + int screen_width = driver->screen.w; + int screen_height = driver->screen.h; int memsize = 2 + 400 / 8 + 2; - uint8_t *buf = screen->pixels; + uint8_t *buf = driver->pixels; - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); + spi_device_acquire_bus(driver->spi_disp.handle, portMAX_DELAY); bool transaction_in_progress = false; for (int ypos = 0; ypos < screen_height; ypos++) { - if (!screen->dma_out && transaction_in_progress) { + if (!driver->dma_out && transaction_in_progress) { spi_transaction_t *trans = NULL; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); + spi_device_get_trans_result(driver->spi_disp.handle, &trans, portMAX_DELAY); } memset(buf + 2, 0xFF, DISPLAY_WIDTH / 8); int xpos = 0; while (xpos < screen_width) { - int drawn_pixels = draw_x(buf + 2, xpos, ypos, items, len); + int drawn_pixels = mono_draw_x(&driver->screen, buf + 2, xpos, ypos, items, len); xpos += drawn_pixels; } - buf[0] = 0x1 | get_vcom(); + buf[0] = 0x1 | next_vcom(driver); buf[1] = ypos + 1; buf[2 + DISPLAY_WIDTH / 8] = 0; buf[2 + DISPLAY_WIDTH / 8 + 1] = 0; - if (screen->dma_out) { + if (driver->dma_out) { if (transaction_in_progress) { spi_transaction_t *trans = NULL; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); + spi_device_get_trans_result(driver->spi_disp.handle, &trans, portMAX_DELAY); } - void *tmp = screen->pixels; - screen->pixels = screen->dma_out; - buf = screen->pixels; - screen->dma_out = tmp; + void *tmp = driver->pixels; + driver->pixels = driver->dma_out; + buf = driver->pixels; + driver->dma_out = tmp; - spi_display_dmawrite(&spi->spi_disp, memsize, screen->dma_out); + spi_display_dma_write(&driver->spi_disp, memsize, driver->dma_out); } else { - spi_display_dmawrite(&spi->spi_disp, memsize, buf); + spi_display_dma_write(&driver->spi_disp, memsize, buf); } transaction_in_progress = true; @@ -172,15 +154,13 @@ static void do_update(Context *ctx, term display_list) if (transaction_in_progress) { spi_transaction_t *trans; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); + spi_device_get_trans_result(driver->spi_disp.handle, &trans, portMAX_DELAY); } - spi_device_release_bus(spi->spi_disp.handle); - destroy_items(items, len); + spi_device_release_bus(driver->spi_disp.handle); + display_items_delete(items, len); } -static void send_message(term pid, term message, GlobalContext *global); - static void process_message(Message *message, Context *ctx) { GenMessage gen_message; @@ -217,78 +197,55 @@ static void process_message(Message *message, Context *ctx) term_put_tuple_element(return_tuple, 0, gen_message.ref); term_put_tuple_element(return_tuple, 1, OK_ATOM); - send_message(gen_message.pid, return_tuple, ctx->global); + display_message_send(gen_message.pid, return_tuple, ctx->global); END_WITH_STACK_HEAP(heap, ctx->global); } -static void process_messages(void *arg) -{ - struct SPI *args = arg; - - while (true) { - Message *message; - xQueueReceive(display_messages_queue, &message, portMAX_DELAY); - process_message(message, args->ctx); - - BEGIN_WITH_STACK_HEAP(1, temp_heap); - mailbox_message_dispose(&message->base, &temp_heap); - END_WITH_STACK_HEAP(temp_heap, args->ctx->global); - } -} - -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx) -{ - MailboxMessage *mbox_msg = mailbox_take_message(&ctx->mailbox); - Message *msg = CONTAINER_OF(mbox_msg, Message, base); - - xQueueSend(display_messages_queue, &msg, 1); - - return NativeContinue; -} - Context *memory_lcd_display_create_port(GlobalContext *global, term opts) { Context *ctx = context_new(global); - ctx->native_handler = display_driver_consume_mailbox; + ctx->native_handler = display_task_consume_mailbox; display_init(ctx, opts); return ctx; } -static void send_message(term pid, term message, GlobalContext *global) -{ - int local_process_id = term_to_local_process_id(pid); - globalcontext_send_message(global, local_process_id, message); -} - static void display_init(Context *ctx, term opts) { - screen = malloc(sizeof(struct Screen)); - // FIXME: hardcoded width and height - screen->w = 400; - screen->h = 240; + GlobalContext *glb = ctx->global; + + term width_term = interop_kv_get_value_default( + opts, ATOM_STR("\x5", "width"), term_from_int(DISPLAY_WIDTH), glb); + term height_term = interop_kv_get_value_default( + opts, ATOM_STR("\x6", "height"), term_from_int(DISPLAY_HEIGHT), glb); + int width = term_to_int(width_term); + int height = term_to_int(height_term); + int memsize = 2 + 400 / 8 + 2; - screen->pixels = heap_caps_malloc(memsize, MALLOC_CAP_DMA); - if (UNLIKELY(!screen->pixels)) { + struct MemoryLCDDriver *driver = malloc(sizeof(struct MemoryLCDDriver)); + + driver->display_args.messages_queue = xQueueCreate(32, sizeof(Message *)); + driver->display_args.process_message_fn = process_message; + driver->display_args.ctx = ctx; + ctx->platform_data = &driver->display_args; + + driver->ctx = ctx; + driver->screen.w = width; + driver->screen.h = height; + driver->vcom = 0; + + driver->pixels = heap_caps_malloc(memsize, MALLOC_CAP_DMA); + if (UNLIKELY(!driver->pixels)) { fprintf(stderr, "failed to allocate buf!\n"); abort(); } - screen->dma_out = heap_caps_malloc(memsize, MALLOC_CAP_DMA); - if (UNLIKELY(!screen->dma_out)) { + driver->dma_out = heap_caps_malloc(memsize, MALLOC_CAP_DMA); + if (UNLIKELY(!driver->dma_out)) { fprintf(stderr, "failed to allocate buf!\n"); abort(); } - display_messages_queue = xQueueCreate(32, sizeof(Message *)); - - GlobalContext *glb = ctx->global; - - struct SPI *spi = malloc(sizeof(struct SPI)); - ctx->platform_data = spi; - - spi->ctx = ctx; - struct SPIDisplayConfig spi_config; spi_display_init_config(&spi_config); spi_config.mode = 0; @@ -298,7 +255,7 @@ static void display_init(Context *ctx, term opts) spi_config.cs_ena_pretrans = 4; // it should be at least 3us spi_config.cs_ena_posttrans = 2; // it should be at least 1us spi_display_parse_config(&spi_config, opts, ctx->global); - spi_display_init(&spi->spi_disp, &spi_config); + spi_display_init(&driver->spi_disp, &spi_config); int en_gpio; bool ok = display_common_gpio_from_opts(opts, ATOM_STR("\x2", "en"), &en_gpio, glb); @@ -308,5 +265,5 @@ static void display_init(Context *ctx, term opts) gpio_set_level(en_gpio, 1); } - xTaskCreate(process_messages, "display", 10000, spi, 1, NULL); + xTaskCreate(display_task_process_messages, "display", 10000, &driver->display_args, 1, NULL); } diff --git a/message_helpers.h b/message_helpers.h deleted file mode 100644 index aa4895a..0000000 --- a/message_helpers.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * This file is part of AtomGL. - * - * Copyright 2022 Davide Bettio - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include - -#include "image_helpers.h" - -struct PendingReply -{ - uint64_t pending_call_ref_ticks; - term pending_call_pid; -}; - -static QueueHandle_t display_messages_queue; - -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx); - -static void send_message(term pid, term message, GlobalContext *global); - -static void process_message(Message *message, Context *ctx) -{ - GenMessage gen_message; - if (UNLIKELY(port_parse_gen_message(message->message, &gen_message) != GenCallMessage)) { - fprintf(stderr, "Received invalid message."); - AVM_ABORT(); - } - - term req = gen_message.req; - if (UNLIKELY(!term_is_tuple(req) || term_get_tuple_arity(req) < 1)) { - AVM_ABORT(); - } - term cmd = term_get_tuple_element(req, 0); - - if (cmd == context_make_atom(ctx, "\x6" - "update")) { - term display_list = term_get_tuple_element(req, 1); - do_update(ctx, display_list); - - } else if (cmd == globalcontext_make_atom(ctx->global, "\xA" "load_image")) { - handle_load_image(req, gen_message.ref, gen_message.pid, ctx); - return; - - } else { -#if REPORT_UNEXPECTED_MSGS - fprintf(stderr, "display: "); - term_display(stderr, req, ctx); - fprintf(stderr, "\n"); -#endif - } - - BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2) + REF_SIZE, heap); - term return_tuple = term_alloc_tuple(2, &heap); - term_put_tuple_element(return_tuple, 0, gen_message.ref); - term_put_tuple_element(return_tuple, 1, OK_ATOM); - - send_message(gen_message.pid, return_tuple, ctx->global); - END_WITH_STACK_HEAP(heap, ctx->global); -} - -static void process_messages(void *arg) -{ - struct SPI *args = arg; - - while (true) { - Message *message; - xQueueReceive(display_messages_queue, &message, portMAX_DELAY); - process_message(message, args->ctx); - - BEGIN_WITH_STACK_HEAP(1, temp_heap); - mailbox_message_dispose(&message->base, &temp_heap); - END_WITH_STACK_HEAP(temp_heap, args->ctx->global); - } -} - -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx) -{ - MailboxMessage *mbox_msg = mailbox_take_message(&ctx->mailbox); - Message *msg = CONTAINER_OF(mbox_msg, Message, base); - - xQueueSend(display_messages_queue, &msg, 1); - - return NativeContinue; -} - -static void send_message(term pid, term message, GlobalContext *global) -{ - int local_process_id = term_to_local_process_id(pid); - globalcontext_send_message(global, local_process_id, message); -} diff --git a/monochrome.h b/mono_draw.c similarity index 60% rename from monochrome.h rename to mono_draw.c index 78e49b1..fc57e13 100644 --- a/monochrome.h +++ b/mono_draw.c @@ -1,7 +1,7 @@ /* * This file is part of AtomGL. * - * Copyright 2022-2024 Davide Bettio + * Copyright 2022-2026 Davide Bettio * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include -#include +#include "mono_draw.h" + +#include +#include +#include + +#include + +#include "display_items.h" +#include "font_data.h" static int get_color(int x, int y, uint8_t r, uint8_t g, uint8_t b) { @@ -57,76 +65,38 @@ static int get_color(int x, int y, uint8_t r, uint8_t g, uint8_t b) return yval >= 128; } -static inline void draw_pixel_x(uint8_t *line_buf, int xpos, int color) +void mono_draw_pixel_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int color) { -#if CHECK_OVERFLOW - if (xpos > DISPLAY_WIDTH) { + if (xpos > screen->w) { fprintf(stderr, "display buffer overflow: %i!\n", xpos); return; } -#endif int bpos = (xpos % 8); line_buf[xpos / 8] = (line_buf[xpos / 8] & ~(0x1 << bpos)) | (color << bpos); } -static int draw_image_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item) +int mono_find_max_line_len(const struct MonoScreen *screen, + BaseDisplayItem items[], size_t items_len, int xpos, int ypos) { - int x = item->x; - int y = item->y; - - int bgcolor_r; - int bgcolor_g; - int bgcolor_b; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor_r = (item->brcolor >> 24) & 0xFF; - bgcolor_g = (item->brcolor >> 16) & 0xFF; - bgcolor_b = (item->brcolor >> 8) & 0xFF; - visible_bg = true; - } else { - bgcolor_r = 0; - bgcolor_g = 0; - bgcolor_b = 0; - visible_bg = false; - } - - int width = item->width; - const char *data = item->data.image_data.pix; - - int drawn_pixels = 0; - - uint32_t *pixels = ((uint32_t *) data) + (ypos - y) * width + (xpos - x); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - uint32_t img_pixel = READ_32_UNALIGNED(pixels); - if ((*pixels >> 24) & 0xFF) { - uint8_t r = img_pixel >> 24; - uint8_t g = (img_pixel >> 16) & 0xFF; - uint8_t b = (img_pixel >> 8) & 0xFF; + int line_len = screen->w - xpos; - uint8_t c = get_color(xpos + drawn_pixels, ypos, r, g, b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); + for (size_t i = 0; i < items_len; i++) { + BaseDisplayItem *item = &items[i]; - } else if (visible_bg) { - uint8_t c = get_color(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); - - } else { - return drawn_pixels; + if ((xpos < item->x) && (ypos >= item->y) && (ypos < item->y + item->height)) { + int len_to_item = item->x - xpos; + line_len = (line_len > len_to_item) ? len_to_item : line_len; } - drawn_pixels++; - pixels++; } - return drawn_pixels; + return line_len; } -static int draw_scaled_cropped_img_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item) +int mono_draw_image_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item) { int x = item->x; int y = item->y; @@ -148,22 +118,11 @@ static int draw_scaled_cropped_img_x(uint8_t *line_buf, int xpos, int ypos, int } int width = item->width; - const char *data = item->data.image_data_with_size.pix; + const char *data = item->data.image_data.pix; int drawn_pixels = 0; - int y_scale = item->y_scale; - int x_scale = item->x_scale; - int img_width = item->data.image_data_with_size.width; - - int source_x = item->source_x; - int source_y = item->source_y; - - uint32_t *pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((xpos - x) / x_scale); - - if (source_x + (width / x_scale) > img_width) { - width = (img_width - source_x) * x_scale; - } + uint32_t *pixels = ((uint32_t *) data) + (ypos - y) * width + (xpos - x); if (width > xpos - x + max_line_len) { width = xpos - x + max_line_len; @@ -176,25 +135,26 @@ static int draw_scaled_cropped_img_x(uint8_t *line_buf, int xpos, int ypos, int uint8_t g = (img_pixel >> 16) & 0xFF; uint8_t b = (img_pixel >> 8) & 0xFF; - uint8_t c = get_color(xpos + drawn_pixels, ypos, r, g, b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); + int c = get_color(xpos + drawn_pixels, ypos, r, g, b); + mono_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); } else if (visible_bg) { - uint8_t c = get_color(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); + int c = get_color(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b); + mono_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); } else { return drawn_pixels; } drawn_pixels++; - //TODO: optimize here - pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + (j / x_scale); + pixels++; } return drawn_pixels; } -static int draw_rect_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item) +int mono_draw_rect_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item) { int x = item->x; int width = item->width; @@ -210,8 +170,8 @@ static int draw_rect_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, } for (int j = xpos - x; j < width; j++) { - uint8_t c = get_color(xpos + drawn_pixels, ypos, r, g, b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); + int c = get_color(xpos + drawn_pixels, ypos, r, g, b); + mono_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); drawn_pixels++; } @@ -219,7 +179,9 @@ static int draw_rect_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, return drawn_pixels; } -static int draw_text_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, BaseDisplayItem *item) +int mono_draw_text_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item) { int x = item->x; int y = item->y; @@ -271,18 +233,138 @@ static int draw_text_x(uint8_t *line_buf, int xpos, int ypos, int max_line_len, } if (opaque) { - uint8_t c = get_color(xpos + drawn_pixels, ypos, fgcolor_r, fgcolor_g, fgcolor_b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); + int c = get_color(xpos + drawn_pixels, ypos, fgcolor_r, fgcolor_g, fgcolor_b); + mono_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); + + } else if (visible_bg) { + int c = get_color(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b); + mono_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); + + } else { + return drawn_pixels; + } + drawn_pixels++; + } + + return drawn_pixels; +} + +int mono_draw_scaled_cropped_img_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item) +{ + int x = item->x; + int y = item->y; + + int bgcolor_r; + int bgcolor_g; + int bgcolor_b; + bool visible_bg; + if (item->brcolor != 0) { + bgcolor_r = (item->brcolor >> 24) & 0xFF; + bgcolor_g = (item->brcolor >> 16) & 0xFF; + bgcolor_b = (item->brcolor >> 8) & 0xFF; + visible_bg = true; + } else { + bgcolor_r = 0; + bgcolor_g = 0; + bgcolor_b = 0; + visible_bg = false; + } + + int width = item->width; + const char *data = item->data.image_data_with_size.pix; + + int drawn_pixels = 0; + + int y_scale = item->y_scale; + int x_scale = item->x_scale; + int img_width = item->data.image_data_with_size.width; + + int source_x = item->source_x; + int source_y = item->source_y; + + uint32_t *pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((xpos - x) / x_scale); + + if (source_x + (width / x_scale) > img_width) { + width = (img_width - source_x) * x_scale; + } + + if (width > xpos - x + max_line_len) { + width = xpos - x + max_line_len; + } + + for (int j = xpos - x; j < width; j++) { + uint32_t img_pixel = READ_32_UNALIGNED(pixels); + if ((*pixels >> 24) & 0xFF) { + uint8_t r = img_pixel >> 24; + uint8_t g = (img_pixel >> 16) & 0xFF; + uint8_t b = (img_pixel >> 8) & 0xFF; + + int c = get_color(xpos + drawn_pixels, ypos, r, g, b); + mono_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); } else if (visible_bg) { - uint8_t c = get_color(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b); - draw_pixel_x(line_buf, xpos + drawn_pixels, c); + int c = get_color(xpos + drawn_pixels, ypos, bgcolor_r, bgcolor_g, bgcolor_b); + mono_draw_pixel_x(screen, line_buf, xpos + drawn_pixels, c); } else { return drawn_pixels; } drawn_pixels++; + pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((j + 1) / x_scale); } return drawn_pixels; } + +int mono_draw_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int ypos, + BaseDisplayItem items[], size_t items_len) +{ + bool below = false; + + for (size_t i = 0; i < items_len; i++) { + BaseDisplayItem *item = &items[i]; + if ((xpos < item->x) || (xpos >= item->x + item->width) || (ypos < item->y) || (ypos >= item->y + item->height)) { + continue; + } + + int max_line_len = below ? 1 : mono_find_max_line_len(screen, items, i, xpos, ypos); + + int drawn_pixels = 0; + switch (items[i].primitive) { + case PrimitiveImage: + //fprintf(stderr, "Image\n"); + drawn_pixels = mono_draw_image_x(screen, line_buf, xpos, ypos, max_line_len, item); + break; + + case PrimitiveScaledCroppedImage: + //fprintf(stderr, "ScaledCroppedImage\n"); + drawn_pixels = mono_draw_scaled_cropped_img_x(screen, line_buf, xpos, ypos, max_line_len, item); + break; + + case PrimitiveRect: + //fprintf(stderr, "Rect\n"); + drawn_pixels = mono_draw_rect_x(screen, line_buf, xpos, ypos, max_line_len, item); + break; + + case PrimitiveText: + //fprintf(stderr, "Text\n"); + drawn_pixels = mono_draw_text_x(screen, line_buf, xpos, ypos, max_line_len, item); + break; + + default: { + fprintf(stderr, "unexpected display list command.\n"); + } + } + + if (drawn_pixels != 0) { + return drawn_pixels; + } + + below = true; + } + + return 1; +} diff --git a/mono_draw.h b/mono_draw.h new file mode 100644 index 0000000..0cda8c8 --- /dev/null +++ b/mono_draw.h @@ -0,0 +1,58 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2022-2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MONO_DRAW_H_ +#define _MONO_DRAW_H_ + +#include "display_items.h" + +struct MonoScreen +{ + int w; + int h; +}; + +void mono_draw_pixel_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int color); + +int mono_draw_image_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item); + +int mono_draw_rect_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item); + +int mono_draw_text_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item); + +int mono_draw_scaled_cropped_img_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int ypos, int max_line_len, + BaseDisplayItem *item); + +int mono_find_max_line_len(const struct MonoScreen *screen, + BaseDisplayItem items[], size_t items_len, int xpos, int ypos); + +int mono_draw_x(const struct MonoScreen *screen, + uint8_t *line_buf, int xpos, int ypos, + BaseDisplayItem items[], size_t items_len); + +#endif diff --git a/oled_commands.c b/oled_commands.c new file mode 100644 index 0000000..a10bde6 --- /dev/null +++ b/oled_commands.c @@ -0,0 +1,148 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "oled_commands.h" + +#include +#include + +// SSD13xx I2C control byte (Co=0, D/C#=0): all subsequent bytes in +// this transaction are interpreted as command bytes (commands and +// their inline parameters). +#define OLED_CTRL_CMD_STREAM 0x00 + +void oled_execute_init_seq(i2c_port_t i2c_num, uint8_t i2c_addr, + const uint8_t *seq, size_t seq_len) +{ + const uint8_t *end = seq + seq_len; + while (seq < end) { + uint8_t cmd_byte = *seq++; + uint8_t flags_len = *seq++; + uint8_t len = flags_len & 0x7F; + + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_addr << 1) | I2C_MASTER_WRITE, true); + i2c_master_write_byte(cmd, OLED_CTRL_CMD_STREAM, true); + i2c_master_write_byte(cmd, cmd_byte, true); + for (uint8_t i = 0; i < len; i++) { + i2c_master_write_byte(cmd, seq[i], true); + } + i2c_master_stop(cmd); + i2c_master_cmd_begin(i2c_num, cmd, 50 / portTICK_PERIOD_MS); + i2c_cmd_link_delete(cmd); + + seq += len; + + if (flags_len & OLED_INIT_SEQ_DELAY) { + vTaskDelay(*seq++ / portTICK_PERIOD_MS); + } + } +} + +// --- Built-in init sequences --- +// +// Transcribed mechanically from the original display_init() body in +// ssd1306_display_driver.c. _Static_assert on sizeof guards against +// miscounted data bytes. + +// clang-format off + +// SSD1306 / SH1106 minimal init. The post-reset defaults are mostly +// correct for these controllers; the only adjustments needed are +// charge-pump enable and the segment-remap / COM-scan flip used by +// most modules. +const uint8_t oled_init_seq_ssd1306[] = { + 0x8D, 1, 0x14, // SET_CHARGE_PUMP + enable + 0xA1, 0, // SET_SEGMENT_REMAP (column 127 -> SEG0) + 0xC8, 0, // SET_COM_SCAN_MODE (remapped) +}; +_Static_assert(sizeof(oled_init_seq_ssd1306) == 7, + "oled_init_seq_ssd1306: miscounted bytes"); +const size_t oled_init_seq_ssd1306_len = sizeof(oled_init_seq_ssd1306); + +// SSD1315 full init. Derived from the u8g2 project (BSD-2-Clause) +// per the comment in the original driver. Standard hardware +// initialization commands defined by the Solomon Systech SSD1315 +// datasheet. +const uint8_t oled_init_seq_ssd1315[] = { + 0xAE, 0, // Display OFF + 0xD5, 1, 0x80, // Display Clock Divide Ratio (0x80 standard) + 0xA8, 1, 0x3F, // Multiplex Ratio (64 MUX) + 0xD3, 1, 0x00, // Display Offset (none) + 0x40, 0, // Display Start Line = 0 + 0x8D, 1, 0x14, // Charge Pump (enable) + 0xA1, 0, // Segment Remap + 0xC8, 0, // COM Scan Mode + 0xDA, 1, 0x12, // COM Pins Hardware Configuration (alternative) + 0x81, 1, 0xCF, // Contrast Control (high contrast per u8x8) + 0xD9, 1, 0xF1, // Pre-charge Period (required for 400 kHz stability) + 0xDB, 1, 0x40, // VCOMH Deselect Level (~0.77x VCC) + 0xA4, 0, // Resume to RAM content display + 0xA6, 0, // Normal Display (not inverted) + 0xAD, 1, 0x10, // Internal IREF Setting +}; +_Static_assert(sizeof(oled_init_seq_ssd1315) == 39, + "oled_init_seq_ssd1315: miscounted bytes"); +const size_t oled_init_seq_ssd1315_len = sizeof(oled_init_seq_ssd1315); + +// clang-format on + +// --- Per-controller descriptors --- + +const struct OLEDDesc oled_desc_ssd1306 = { + .name = "Solomon Systech SSD1306", + .native_width = 128, + .native_height = 64, + .i2c_address = 0x3C, + + .init_seq = oled_init_seq_ssd1306, + .init_seq_len = sizeof(oled_init_seq_ssd1306), + + .column_reset_per_page = false, + .scanline_prefix_pad_bytes = 0, +}; + +const struct OLEDDesc oled_desc_ssd1315 = { + .name = "Solomon Systech SSD1315", + .native_width = 128, + .native_height = 64, + .i2c_address = 0x3C, + + .init_seq = oled_init_seq_ssd1315, + .init_seq_len = sizeof(oled_init_seq_ssd1315), + + .column_reset_per_page = true, + .scanline_prefix_pad_bytes = 0, +}; + +const struct OLEDDesc oled_desc_sh1106 = { + .name = "Sino Wealth SH1106", + .native_width = 128, + .native_height = 64, + .i2c_address = 0x3C, + + // SH1106 shares the SSD1306 minimal init. + .init_seq = oled_init_seq_ssd1306, + .init_seq_len = sizeof(oled_init_seq_ssd1306), + + .column_reset_per_page = true, + .scanline_prefix_pad_bytes = 2, +}; diff --git a/oled_commands.h b/oled_commands.h new file mode 100644 index 0000000..bb8c423 --- /dev/null +++ b/oled_commands.h @@ -0,0 +1,107 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _OLED_COMMANDS_H_ +#define _OLED_COMMANDS_H_ + +#include +#include +#include + +#include + +// --- Init sequence byte-array format --- +// +// Each entry: [CMD] [FLAGS_LEN] [DATA_0 ... DATA_N] [DELAY_MS] +// CMD: OLED command byte +// FLAGS_LEN: bits 6:0 = data byte count (0-127) +// bit 7 = delay flag (DELAY_MS byte follows data) +// DELAY_MS: delay in milliseconds (0-255), present only if flag set +// +// The sequence is length-bounded: callers pass seq_len and the +// executor walks the buffer until exhausted. The format +// intentionally matches epaper_commands.h so the codebase converges +// on one init-sequence convention; current OLED init steps don't +// need delays, but future variants can opt in without a format +// change. +// +// Driver-controlled finalization (the optional "invert" command and +// the unconditional "display ON") is NOT encoded in init sequences; +// it stays in the driver because it depends on a runtime opt. + +#define OLED_INIT_SEQ_DELAY 0x80 + +// Execute an init sequence over an I2C bus. Owns the transaction +// boundary: emits one i2c_cmd_link transaction per init step so that +// the optional inter-step delay (vTaskDelay) actually waits between +// commands rather than being a no-op inside a queued cmd_link. Uses +// the SSD13xx command-stream control byte (Co=0, D/C#=0) so a +// command and its parameters can ride together in one transaction. +void oled_execute_init_seq(i2c_port_t i2c_num, uint8_t i2c_addr, + const uint8_t *seq, size_t seq_len); + +// Built-in init sequences. SSD1306 and SH1106 share the minimal +// 4-step charge-pump/remap init; SSD1315 has its own full reset +// sequence derived from u8g2. Each array is paired with a size_t +// constant giving its length; callers pass both to +// oled_execute_init_seq(). +extern const uint8_t oled_init_seq_ssd1306[]; +extern const size_t oled_init_seq_ssd1306_len; +extern const uint8_t oled_init_seq_ssd1315[]; +extern const size_t oled_init_seq_ssd1315_len; + +// --- Per-controller descriptor --- +// +// Captures every controller-specific knob so a single unified driver +// can drive all three SSD13xx / SH1106 variants by compatible-string +// dispatch. The struct carries no function pointers: the variation +// across SSD1306, SSD1315 and SH1106 is entirely data. + +struct OLEDDesc +{ + const char *name; + int native_width; + int native_height; + uint8_t i2c_address; + + // One-time init sequence (length-framed format documented above). + const uint8_t *init_seq; + size_t init_seq_len; + + // True for controllers that require an explicit column-address + // reset (lower nibble 0x00, upper nibble 0x10) before writing + // each page of pixel data. SSD1315 and SH1106 need this; the + // bare SSD1306 retains its column pointer across pages and does + // not. + bool column_reset_per_page; + + // Number of zero bytes to write at the start of each page's + // data stream. SH1106 modules expose a 128-pixel viewport on a + // 132-pixel-wide controller, so the first two RAM columns are + // off-screen and skipped by writing 0x00 0x00 before the + // visible pixels. Zero for SSD1306 and SSD1315. + uint8_t scanline_prefix_pad_bytes; +}; + +extern const struct OLEDDesc oled_desc_ssd1306; +extern const struct OLEDDesc oled_desc_ssd1315; +extern const struct OLEDDesc oled_desc_sh1106; + +#endif diff --git a/ssd1306_display_driver.c b/oled_display_driver.c similarity index 50% rename from ssd1306_display_driver.c rename to oled_display_driver.c index 785734e..e299a49 100644 --- a/ssd1306_display_driver.c +++ b/oled_display_driver.c @@ -27,21 +27,29 @@ #include #include +#include +#include #include +#include +#include +#include +#include #include #include "display_common.h" +#include "display_message.h" +#include "display_task.h" +#include "image_helpers.h" +#include "mono_draw.h" +#include "oled_commands.h" -#define TAG "SSD1306" +#define TAG "oled_display" #define DISPLAY_WIDTH 128 #define DISPLAY_HEIGHT 64 #define PAGE_HEIGHT 8 #define PAGES_NUM 8 -#define CHAR_WIDTH 8 - -#define I2C_ADDRESS 0x3C #define CTRL_BYTE_CMD_SINGLE 0x80 #define CTRL_BYTE_CMD_STREAM 0x00 @@ -49,32 +57,42 @@ #define CMD_DISPLAY_INVERTED 0xA7 #define CMD_DISPLAY_ON 0xAF -#define CMD_SET_SEGMENT_REMAP 0xA1 -#define CMD_SET_COM_SCAN_MODE 0xC8 -#define CMD_SET_CHARGE_PUMP 0x8D - -typedef enum -{ - DISPLAY_SSD1306, - DISPLAY_SSD1315, - DISPLAY_SH1106, -} display_type_t; -// TODO: let's change name, since also non SPI display are supported now -struct SPI +struct OLEDDriver { term i2c_host; - display_type_t type; + const struct OLEDDesc *desc; Context *ctx; + + struct MonoScreen screen; + + struct DisplayTaskArgs display_args; }; -static void do_update(Context *ctx, term display_list); +#define OLED_DRIVER_FROM_CTX(ctx) \ + CONTAINER_OF((struct DisplayTaskArgs *) (ctx)->platform_data, struct OLEDDriver, display_args) -#include "font.c" +#include "font_data.h" #include "display_items.h" -#include "draw_common.h" -#include "monochrome.h" -#include "message_helpers.h" + +static const struct { + const char *compat; + const struct OLEDDesc *desc; +} oled_compat_table[] = { + { "solomon-systech,ssd1306", &oled_desc_ssd1306 }, + { "solomon-systech,ssd1315", &oled_desc_ssd1315 }, + { "sino-wealth,sh1106", &oled_desc_sh1106 }, +}; + +static const struct OLEDDesc *oled_desc_for_compatible(const char *compat) +{ + for (size_t i = 0; i < sizeof(oled_compat_table) / sizeof(oled_compat_table[0]); i++) { + if (!strcmp(compat, oled_compat_table[i].compat)) { + return oled_compat_table[i].desc; + } + } + return NULL; +} static void do_update(Context *ctx, term display_list) { @@ -85,20 +103,20 @@ static void do_update(Context *ctx, term display_list) term t = display_list; for (int i = 0; i < len; i++) { - init_item(&items[i], term_get_list_head(t), ctx); + display_items_init_item(&items[i], term_get_list_head(t), ctx); t = term_get_list_tail(t); } int screen_width = DISPLAY_WIDTH; int screen_height = DISPLAY_HEIGHT; - struct SPI *spi = ctx->platform_data; + struct OLEDDriver *driver = OLED_DRIVER_FROM_CTX(ctx); int memsize = (DISPLAY_WIDTH * (PAGE_HEIGHT + 1)) / sizeof(uint8_t); uint8_t *buf = malloc(memsize); memset(buf, 0, memsize); i2c_port_t i2c_num; - if (i2c_driver_acquire(spi->i2c_host, &i2c_num, ctx->global) != I2CAcquireOk) { + if (i2c_driver_acquire(driver->i2c_host, &i2c_num, ctx->global) != I2CAcquireOk) { fprintf(stderr, "Invalid I2C peripheral\n"); return; } @@ -106,7 +124,7 @@ static void do_update(Context *ctx, term display_list) for (int ypos = 0; ypos < screen_height; ypos++) { int xpos = 0; while (xpos < screen_width) { - int drawn_pixels = draw_x(buf, xpos, ypos, items, len); + int drawn_pixels = mono_draw_x(&driver->screen, buf, xpos, ypos, items, len); xpos += drawn_pixels; } @@ -119,13 +137,12 @@ static void do_update(Context *ctx, term display_list) i2c_cmd_handle_t cmd; cmd = i2c_cmd_link_create(); i2c_master_start(cmd); - i2c_master_write_byte(cmd, (I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true); + i2c_master_write_byte(cmd, (driver->desc->i2c_address << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(cmd, CTRL_BYTE_CMD_SINGLE, true); i2c_master_write_byte(cmd, 0xB0 | ypos / 8, true); - if (spi->type == DISPLAY_SH1106 || spi->type == DISPLAY_SSD1315) { - // SSD1315 and SH1106 require explicit column address reset + if (driver->desc->column_reset_per_page) { i2c_master_write_byte(cmd, CTRL_BYTE_CMD_SINGLE, true); i2c_master_write_byte(cmd, 0x00, true); i2c_master_write_byte(cmd, CTRL_BYTE_CMD_SINGLE, true); @@ -133,11 +150,10 @@ static void do_update(Context *ctx, term display_list) } i2c_master_write_byte(cmd, CTRL_BYTE_DATA_STREAM, true); - - if (spi->type == DISPLAY_SH1106) { - // add 2 empty pages on sh1106 since it can have up to 132 pixels - // and 128 pixel screen starts at (2, 0) - i2c_master_write_byte(cmd, 0, true); + // Pad the data stream with leading zero bytes for controllers + // whose RAM is wider than the visible pixel area (SH1106 exposes + // 128 of 132 columns starting at offset 2). + for (uint8_t k = 0; k < driver->desc->scanline_prefix_pad_bytes; k++) { i2c_master_write_byte(cmd, 0, true); } @@ -145,12 +161,6 @@ static void do_update(Context *ctx, term display_list) i2c_master_write_byte(cmd, out_buf[j], true); } - // no need to send the last 2 page, the position will be set on next line again - // if (spi->type == DISPLAY_SH1106) { - // i2c_master_write_byte(cmd, 0, true); - // i2c_master_write_byte(cmd, 0, true); - // } - i2c_master_stop(cmd); i2c_master_cmd_begin(i2c_num, cmd, 100 / portTICK_PERIOD_MS); i2c_cmd_link_delete(cmd); @@ -159,10 +169,48 @@ static void do_update(Context *ctx, term display_list) } } - i2c_driver_release(spi->i2c_host, ctx->global); + i2c_driver_release(driver->i2c_host, ctx->global); free(buf); - destroy_items(items, len); + display_items_delete(items, len); +} + +static void process_message(Message *message, Context *ctx) +{ + GenMessage gen_message; + if (UNLIKELY(port_parse_gen_message(message->message, &gen_message) != GenCallMessage)) { + fprintf(stderr, "Received invalid message."); + AVM_ABORT(); + } + + term req = gen_message.req; + if (UNLIKELY(!term_is_tuple(req) || term_get_tuple_arity(req) < 1)) { + AVM_ABORT(); + } + term cmd = term_get_tuple_element(req, 0); + + if (cmd == context_make_atom(ctx, "\x6" + "update")) { + term display_list = term_get_tuple_element(req, 1); + do_update(ctx, display_list); + + } else if (cmd == globalcontext_make_atom(ctx->global, "\xA" "load_image")) { + handle_load_image(req, gen_message.ref, gen_message.pid, ctx); + return; + + } else { + fprintf(stderr, "display: "); + term_display(stderr, req, ctx); + fprintf(stderr, "\n"); + } + + BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2) + REF_SIZE, heap); + term return_tuple = term_alloc_tuple(2, &heap); + term_put_tuple_element(return_tuple, 0, gen_message.ref); + term_put_tuple_element(return_tuple, 1, OK_ATOM); + + display_message_send(gen_message.pid, return_tuple, ctx->global); + END_WITH_STACK_HEAP(heap, ctx->global); } static void display_init(Context *ctx, term opts) @@ -178,31 +226,35 @@ static void display_init(Context *ctx, term opts) bool invert = interop_kv_get_value(opts, ATOM_STR("\x6", "invert"), glb) == TRUE_ATOM; - display_messages_queue = xQueueCreate(32, sizeof(Message *)); + struct OLEDDriver *driver = malloc(sizeof(struct OLEDDriver)); - struct SPI *spi = malloc(sizeof(struct SPI)); - ctx->platform_data = spi; + driver->display_args.messages_queue = xQueueCreate(32, sizeof(Message *)); + driver->display_args.process_message_fn = process_message; + driver->display_args.ctx = ctx; + ctx->platform_data = &driver->display_args; - spi->ctx = ctx; - spi->type = DISPLAY_SSD1306; // Default to SSD1306 + driver->ctx = ctx; - term compat_value_term = interop_kv_get_value_default(opts, ATOM_STR("\xA", "compatible"), term_nil(), ctx->global); + term compat_value_term = interop_kv_get_value_default( + opts, ATOM_STR("\xA", "compatible"), term_nil(), ctx->global); int str_ok; char *compat_string = interop_term_to_string(compat_value_term, &str_ok); - - if (!(str_ok && compat_string)) { - ESP_LOGE(TAG, "No Compatible Device Found."); - return; + const struct OLEDDesc *desc = NULL; + if (str_ok && compat_string) { + desc = oled_desc_for_compatible(compat_string); } - - if (!strcmp(compat_string, "sino-wealth,sh1106")) { - spi->type = DISPLAY_SH1106; - } else if (!strcmp(compat_string, "solomon-systech,ssd1315")) { - spi->type = DISPLAY_SSD1315; + if (!desc) { + ESP_LOGE(TAG, "Failed init: unknown or missing compatible '%s'.", + compat_string ? compat_string : "(null)"); + free(compat_string); + return; } - free(compat_string); + driver->desc = desc; + driver->screen.w = desc->native_width; + driver->screen.h = desc->native_height; + int reset_gpio; if (!display_common_gpio_from_opts(opts, ATOM_STR("\x5", "reset"), &reset_gpio, glb)) { ESP_LOGI(TAG, "Reset GPIO not configured."); @@ -218,88 +270,38 @@ static void display_init(Context *ctx, term opts) fprintf(stderr, "Invalid I2C peripheral\n"); return; } - spi->i2c_host = i2c_host; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + driver->i2c_host = i2c_host; + oled_execute_init_seq(i2c_num, desc->i2c_address, desc->init_seq, desc->init_seq_len); + + // Driver-controlled finalization: optional invert, then display ON. + // These depend on a runtime opt and are not panel data, so they + // stay outside the per-variant init array. + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); i2c_master_start(cmd); - i2c_master_write_byte(cmd, (I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true); + i2c_master_write_byte(cmd, (desc->i2c_address << 1) | I2C_MASTER_WRITE, true); i2c_master_write_byte(cmd, CTRL_BYTE_CMD_STREAM, true); - - if (spi->type == DISPLAY_SSD1315) { - /* - * Init sequence derived from u8g2 project (BSD-2-Clause). - * Source: https://github.com/olikraus/u8g2 - * - * These values are standard hardware initialization commands - * defined by the Solomon Systech SSD1315 datasheet. - */ - - i2c_master_write_byte(cmd, 0xAE, true); // Display OFF - - i2c_master_write_byte(cmd, 0xD5, true); // Set Display Clock Divide Ratio / Oscillator Frequency - i2c_master_write_byte(cmd, 0x80, true); // 0x80 is standard/stable - - i2c_master_write_byte(cmd, 0xA8, true); // Set Multiplex Ratio - i2c_master_write_byte(cmd, 0x3F, true); // 64 MUX - - i2c_master_write_byte(cmd, 0xD3, true); // Set Display Offset - i2c_master_write_byte(cmd, 0x00, true); // No offset - - i2c_master_write_byte(cmd, 0x40, true); // Set Display Start Line to 0 - - i2c_master_write_byte(cmd, 0x8D, true); // Set Charge Pump - i2c_master_write_byte(cmd, 0x14, true); // Enable Charge Pump - - i2c_master_write_byte(cmd, 0xA1, true); // Set Segment Remap - i2c_master_write_byte(cmd, 0xC8, true); // Set COM Scan Mode - - i2c_master_write_byte(cmd, 0xDA, true); // Set COM Pins Hardware Configuration - i2c_master_write_byte(cmd, 0x12, true); // Alternative COM pin config - - i2c_master_write_byte(cmd, 0x81, true); // Set Contrast Control - i2c_master_write_byte(cmd, 0xCF, true); // Use High Contrast (0xCF) as per u8x8 - - i2c_master_write_byte(cmd, 0xD9, true); // Set Pre-charge Period - i2c_master_write_byte(cmd, 0xF1, true); // 0xF1 is required for stable 400kHz operation - - i2c_master_write_byte(cmd, 0xDB, true); // Set VCOMH Deselect Level - i2c_master_write_byte(cmd, 0x40, true); // 0x40 (approx 0.77x VCC) - - i2c_master_write_byte(cmd, 0xA4, true); // Resume to RAM content display - i2c_master_write_byte(cmd, 0xA6, true); // Normal Display (not inverted) - - i2c_master_write_byte(cmd, 0xAD, true); // Internal IREF Setting - i2c_master_write_byte(cmd, 0x10, true); // Internal Iref - } else { - i2c_master_write_byte(cmd, CMD_SET_CHARGE_PUMP, true); - i2c_master_write_byte(cmd, 0x14, true); - - i2c_master_write_byte(cmd, CMD_SET_SEGMENT_REMAP, true); - i2c_master_write_byte(cmd, CMD_SET_COM_SCAN_MODE, true); - } - if (invert) { i2c_master_write_byte(cmd, CMD_DISPLAY_INVERTED, true); } - i2c_master_write_byte(cmd, CMD_DISPLAY_ON, true); i2c_master_stop(cmd); esp_err_t res = i2c_master_cmd_begin(i2c_num, cmd, 50 / portTICK_PERIOD_MS); if (res != ESP_OK) { - ESP_LOGE(TAG, "ssd1306/ssd1315 OLED configuration failed. error: 0x%.2X", res); + ESP_LOGE(TAG, "%s configuration failed. error: 0x%.2X", desc->name, res); } else { - xTaskCreate(process_messages, "display", 10000, spi, 1, NULL); + xTaskCreate(display_task_process_messages, "display", 10000, &driver->display_args, 1, NULL); } i2c_cmd_link_delete(cmd); i2c_driver_release(i2c_host, glb); } -Context *ssd1306_display_create_port(GlobalContext *global, term opts) +Context *oled_display_create_port(GlobalContext *global, term opts) { Context *ctx = context_new(global); - ctx->native_handler = display_driver_consume_mailbox; + ctx->native_handler = display_task_consume_mailbox; display_init(ctx, opts); return ctx; diff --git a/sdl_display/CMakeLists.txt b/sdl_display/CMakeLists.txt index 6df9ac7..0d95983 100644 --- a/sdl_display/CMakeLists.txt +++ b/sdl_display/CMakeLists.txt @@ -43,7 +43,7 @@ endif() set(CMAKE_SHARED_LIBRARY_PREFIX "") -add_library(avm_display_port_driver SHARED display.c ufontlib.c ../image_helpers.c ../spng.c) +add_library(avm_display_port_driver SHARED display.c ../ufontlib.c ../image_helpers.c ../spng.c ../font_data.c ../display_items.c ../display_message.c) if (AVM_DISABLE_SMP) target_compile_definitions(avm_display_port_driver PUBLIC AVM_NO_SMP) @@ -65,6 +65,7 @@ if (ATOMIC_POINTER_LOCK_FREE_IS_TWO) target_compile_definitions(avm_display_port_driver PUBLIC HAVE_ATOMIC) endif() +target_compile_definitions(avm_display_port_driver PRIVATE ENABLE_UFONT) target_link_libraries(avm_display_port_driver ${SDL_LIBRARY} ${ZLIB_LIBRARIES}) set_property(TARGET avm_display_port_driver PROPERTY C_STANDARD 11) set_property(TARGET avm_display_port_driver PROPERTY PREFIX "") diff --git a/sdl_display/display.c b/sdl_display/display.c index 2ab01d5..8dc0838 100644 --- a/sdl_display/display.c +++ b/sdl_display/display.c @@ -31,16 +31,16 @@ #include #include -#include "ufontlib.h" +#include "../ufontlib.h" #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 240 #define BPP 4 #define DEPTH 32 -#define CHAR_WIDTH 8 #include "../display_items.h" -#include "../font.c" +#include "../display_message.h" +#include "../font_data.h" #include "../image_helpers.h" struct DisplayOpts @@ -125,17 +125,17 @@ static bool cmp_display_item(BaseDisplayItem *a, BaseDisplayItem *b) } switch (a->primitive) { - case Image: + case PrimitiveImage: return a->data.image_data.pix == b->data.image_data.pix; - case Rect: + case PrimitiveRect: return true; - case Text: + case PrimitiveText: return (a->data.text_data.fgcolor == b->data.text_data.fgcolor) && !strcmp(a->data.text_data.text, b->data.text_data.text); - case ScaledCroppedImage: + case PrimitiveScaledCroppedImage: return (a->data.image_data.pix == b->data.image_data.pix) && (a->x_scale == b->x_scale) && (a->y_scale == b->y_scale) && (a->source_x == b->source_x) && (a->source_y == b->source_y); @@ -230,28 +230,6 @@ static inline Uint32 uint32_color_to_surface(struct Screen *screen, uint32_t col return SDL_MapRGB(screen->format, (color >> 24) & 0xFF, (color >> 16) & 0xFF, (color >> 8) & 0xFF); } -struct Surface -{ - int width; - int height; - void *buffer; -}; - -void epd_draw_pixel(int xpos, int ypos, uint8_t color, void *buffer) -{ - struct Surface *surface = buffer; - - if (xpos < 0 || ypos < 0 || xpos >= surface->width || ypos >= surface->height) { - return; - } - - Uint32 *pixmem32b = (Uint32 *) (((uint8_t *) surface->buffer) + surface->width * ypos * BPP + xpos * BPP); - - //TODO: handle other colors than black - UNUSED(color); - *pixmem32b = 0xFF000000; -} - static int draw_image_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) { int x = item->x; @@ -343,7 +321,7 @@ static int draw_scaled_cropped_img_x(int xpos, int ypos, int max_line_len, BaseD return drawn_pixels; } drawn_pixels++; - pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + (j / x_scale); + pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((j + 1) / x_scale); } return drawn_pixels; @@ -425,11 +403,11 @@ static int draw_text_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *it return drawn_pixels; } -static int find_max_line_len(BaseDisplayItem *items, int count, int xpos, int ypos) +static int find_max_line_len(BaseDisplayItem items[], size_t items_len, int xpos, int ypos) { int line_len = screen->w - xpos; - for (int i = 0; i < count; i++) { + for (size_t i = 0; i < items_len; i++) { BaseDisplayItem *item = &items[i]; if ((xpos < item->x) && (ypos >= item->y) && (ypos < item->y + item->height)) { @@ -441,11 +419,11 @@ static int find_max_line_len(BaseDisplayItem *items, int count, int xpos, int yp return line_len; } -static int draw_x(int xpos, int ypos, BaseDisplayItem *items, int items_count) +static int draw_x(int xpos, int ypos, BaseDisplayItem items[], size_t items_len) { bool below = false; - for (int i = 0; i < items_count; i++) { + for (size_t i = 0; i < items_len; i++) { BaseDisplayItem *item = &items[i]; if ((xpos < item->x) || (xpos >= item->x + item->width) || (ypos < item->y) || (ypos >= item->y + item->height)) { continue; @@ -455,19 +433,19 @@ static int draw_x(int xpos, int ypos, BaseDisplayItem *items, int items_count) int drawn_pixels = 0; switch (items[i].primitive) { - case Image: + case PrimitiveImage: drawn_pixels = draw_image_x(xpos, ypos, max_line_len, item); break; - case ScaledCroppedImage: + case PrimitiveScaledCroppedImage: drawn_pixels = draw_scaled_cropped_img_x(xpos, ypos, max_line_len, item); break; - case Rect: + case PrimitiveRect: drawn_pixels = draw_rect_x(xpos, ypos, max_line_len, item); break; - case Text: + case PrimitiveText: drawn_pixels = draw_text_x(xpos, ypos, max_line_len, item); break; @@ -495,7 +473,7 @@ static void do_update(Context *ctx, term display_list) term t = display_list; for (int i = 0; i < len; i++) { - init_item(&items[i], term_get_list_head(t), ctx); + display_items_init_item(&items[i], term_get_list_head(t), ctx); t = term_get_list_tail(t); } @@ -503,7 +481,7 @@ static void do_update(Context *ctx, term display_list) damaged.valid = false; dumb_diff(prev_items, prev_items_len, items, len, &damaged); if (prev_items) { - destroy_items(prev_items, prev_items_len); + display_items_delete(prev_items, prev_items_len); destroy_message(prev_message, ctx->global); } prev_items = items; @@ -601,10 +579,11 @@ static void process_message(Context *ctx) term font_bin = term_get_tuple_element(req, 2); EpdFont *loaded_font = ufont_parse(term_binary_data(font_bin), term_binary_size(font_bin)); - AtomString handle_atom = globalcontext_atomstring_from_term(ctx->global, term_get_tuple_element(req, 1)); - char handle[255]; - atom_string_to_c(handle_atom, handle, sizeof(handle)); - ufont_manager_register(ufont_manager, handle, loaded_font); + char *handle = interop_atom_to_string(ctx, term_get_tuple_element(req, 1)); + if (handle != NULL) { + ufont_manager_register(ufont_manager, handle, loaded_font); + free(handle); + } } else { fprintf(stderr, "unexpected command: "); @@ -650,12 +629,6 @@ static NativeHandlerResult consume_display_mailbox(Context *ctx) return NativeContinue; } -static void send_message(term pid, term message, GlobalContext *global) -{ - int local_process_id = term_to_local_process_id(pid); - globalcontext_send_message(global, local_process_id, message); -} - static inline int replace_new_line(int c) { return c == '\r' ? '\n' : c; @@ -739,7 +712,7 @@ void send_keyboard_event(struct KeyboardEvent *keyb, Context *ctx) term_put_tuple_element(event_tuple, 2, term_from_int(millis)); term_put_tuple_element(event_tuple, 3, event_data_tuple); - send_message(keyboard_pid, event_tuple, glb); + display_message_send(keyboard_pid, event_tuple, glb); END_WITH_STACK_HEAP(heap, glb); } @@ -811,7 +784,7 @@ void send_mouse_event(struct MouseEvent *mouse, Context *ctx) term_put_tuple_element(event_tuple, 2, term_from_int(millis)); term_put_tuple_element(event_tuple, 3, event_data_tuple); - send_message(keyboard_pid, event_tuple, glb); + display_message_send(keyboard_pid, event_tuple, glb); END_WITH_STACK_HEAP(heap, glb); } diff --git a/spi_dc_driver.c b/spi_dc_driver.c new file mode 100644 index 0000000..e9933e9 --- /dev/null +++ b/spi_dc_driver.c @@ -0,0 +1,55 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "spi_dc_driver.h" + +#include + +#include +#include + +void spi_dc_write_data(struct SPIDCBus *bus, uint32_t data) +{ + spi_device_acquire_bus(bus->spi_disp.handle, portMAX_DELAY); + spi_display_write(&bus->spi_disp, 8, data); + spi_device_release_bus(bus->spi_disp.handle); +} + +void spi_dc_write_command(struct SPIDCBus *bus, uint8_t cmd) +{ + gpio_set_level(bus->dc_gpio, 0); + spi_dc_write_data(bus, cmd); + gpio_set_level(bus->dc_gpio, 1); +} + +void spi_dc_write_cmd_data(struct SPIDCBus *bus, uint8_t cmd, const uint8_t *data, size_t data_len) +{ + spi_dc_write_command(bus, cmd); + for (int i = 0; i < data_len; i++) { + spi_dc_write_data(bus, data[i]); + } +} + +void spi_dc_write_data_n(struct SPIDCBus *bus, const uint8_t *data, size_t data_len) +{ + for (size_t i = 0; i < data_len; i++) { + spi_dc_write_data(bus, data[i]); + } +} diff --git a/spi_dc_driver.h b/spi_dc_driver.h new file mode 100644 index 0000000..248fb25 --- /dev/null +++ b/spi_dc_driver.h @@ -0,0 +1,40 @@ +/* + * This file is part of AtomGL. + * + * Copyright 2026 Davide Bettio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _SPI_DC_DRIVER_H_ +#define _SPI_DC_DRIVER_H_ + +#include +#include + +#include "spi_display.h" + +struct SPIDCBus +{ + struct SPIDisplay spi_disp; + int dc_gpio; +}; + +void spi_dc_write_data(struct SPIDCBus *bus, uint32_t data); +void spi_dc_write_command(struct SPIDCBus *bus, uint8_t cmd); +void spi_dc_write_cmd_data(struct SPIDCBus *bus, uint8_t cmd, const uint8_t *data, size_t data_len); +void spi_dc_write_data_n(struct SPIDCBus *bus, const uint8_t *data, size_t data_len); + +#endif diff --git a/spi_display.c b/spi_display.c index c09e002..8487146 100644 --- a/spi_display.c +++ b/spi_display.c @@ -33,40 +33,40 @@ #include "display_common.h" -bool spi_display_dmawrite(struct SPIDisplay *spi_data, int data_len, const void *data) +bool spi_display_dma_write(struct SPIDisplay *spi_disp, size_t data_len, const void *data) { - memset(&spi_data->transaction, 0, sizeof(spi_transaction_t)); + memset(&spi_disp->transaction, 0, sizeof(spi_transaction_t)); - spi_data->transaction.flags = 0; - spi_data->transaction.length = data_len * 8; - spi_data->transaction.addr = 0; - spi_data->transaction.tx_buffer = data; + spi_disp->transaction.flags = 0; + spi_disp->transaction.length = data_len * 8; + spi_disp->transaction.addr = 0; + spi_disp->transaction.tx_buffer = data; - int ret = spi_device_queue_trans(spi_data->handle, &spi_data->transaction, portMAX_DELAY); + int ret = spi_device_queue_trans(spi_disp->handle, &spi_disp->transaction, portMAX_DELAY); if (UNLIKELY(ret != ESP_OK)) { - fprintf(stderr, "spidmawrite: transmit error\n"); + fprintf(stderr, "spi_display_dma_write: transmit error\n"); return false; } return true; } -bool spi_display_write(struct SPIDisplay *spi_data, int data_len, uint32_t data) +bool spi_display_write(struct SPIDisplay *spi_disp, size_t data_len, uint32_t data) { - memset(&spi_data->transaction, 0, sizeof(spi_transaction_t)); + memset(&spi_disp->transaction, 0, sizeof(spi_transaction_t)); uint32_t tx_data = SPI_SWAP_DATA_TX(data, data_len); - spi_data->transaction.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA; - spi_data->transaction.length = data_len; - spi_data->transaction.addr = 0; - spi_data->transaction.tx_data[0] = tx_data; - spi_data->transaction.tx_data[1] = (tx_data >> 8) & 0xFF; - spi_data->transaction.tx_data[2] = (tx_data >> 16) & 0xFF; - spi_data->transaction.tx_data[3] = (tx_data >> 24) & 0xFF; + spi_disp->transaction.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA; + spi_disp->transaction.length = data_len; + spi_disp->transaction.addr = 0; + spi_disp->transaction.tx_data[0] = tx_data; + spi_disp->transaction.tx_data[1] = (tx_data >> 8) & 0xFF; + spi_disp->transaction.tx_data[2] = (tx_data >> 16) & 0xFF; + spi_disp->transaction.tx_data[3] = (tx_data >> 24) & 0xFF; // this function is meant for small amount of data so polling is fine here - int ret = spi_device_polling_transmit(spi_data->handle, &spi_data->transaction); + int ret = spi_device_polling_transmit(spi_disp->handle, &spi_disp->transaction); if (UNLIKELY(ret != ESP_OK)) { fprintf(stderr, "spiwrite: transmit error\n"); return false; @@ -88,8 +88,20 @@ bool spi_display_parse_config(struct SPIDisplayConfig *spi_config, term opts, Gl term spi_port = interop_proplist_get_value(opts, spi_host_atom); ok = spi_driver_get_peripheral(spi_port, &spi_config->host_dev, global); + if (!ok) { + return false; + } - return ok; + term clock_speed_hz = interop_kv_get_value_default( + opts, ATOM_STR("\xE", "clock_speed_hz"), term_nil(), global); + if (clock_speed_hz != term_nil()) { + if (!term_is_integer(clock_speed_hz)) { + return false; + } + spi_config->clock_speed_hz = term_to_int(clock_speed_hz); + } + + return true; } bool spi_display_init(struct SPIDisplay *spi_disp, struct SPIDisplayConfig *spi_config) diff --git a/spi_display.h b/spi_display.h index b3b6a98..925c78f 100644 --- a/spi_display.h +++ b/spi_display.h @@ -24,6 +24,7 @@ #include #include +#include #include @@ -46,8 +47,8 @@ struct SPIDisplayConfig }; bool spi_display_init(struct SPIDisplay *spi_disp, struct SPIDisplayConfig *spi_config); -bool spi_display_dmawrite(struct SPIDisplay *spi_data, int data_len, const void *data); -bool spi_display_write(struct SPIDisplay *spi_data, int data_len, uint32_t data); +bool spi_display_dma_write(struct SPIDisplay *spi_disp, size_t data_len, const void *data); +bool spi_display_write(struct SPIDisplay *spi_disp, size_t data_len, uint32_t data); void spi_display_init_config(struct SPIDisplayConfig *spi_config); bool spi_display_parse_config(struct SPIDisplayConfig *spi_config, term opts, GlobalContext *global); diff --git a/st7789_display_driver.c b/st7789_display_driver.c deleted file mode 100644 index c20a85e..0000000 --- a/st7789_display_driver.c +++ /dev/null @@ -1,998 +0,0 @@ -/* - * This file is part of AtomGL. - * - * Copyright 2020-2024 Davide Bettio - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "display_driver.h" - -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "backlight_gpio.h" -#include "display_common.h" -#include "display_items.h" -#include "image_helpers.h" -#include "spi_display.h" - -// if needed it can be lowered to 27000000, while maximum is 62.5 Mhz -#define SPI_CLOCK_HZ 40000000 -#define SPI_MODE 0 - -#define CHAR_WIDTH 8 - -#define ST7789_SWRESET 0x01 -#define ST7789_SLPIN 0x10 -#define ST7789_SLPOUT 0x11 -#define ST7789_NORON 0x13 -#define ST7789_INVON 0x21 -#define ST7789_DISPOFF 0x28 -#define ST7789_DISPON 0x29 -#define ST7789_CASET 0x2A -#define ST7789_RASET 0x2B -#define ST7789_RAMWR 0x2C -#define ST7789_MADCTL 0x36 -#define ST7789_COLMOD 0x3A -#define ST7789_RAMCTRL 0xB0 -#define ST7789_PORCTRL 0xB2 -#define ST7789_GCTRL 0xB7 -#define ST7789_VCOMS 0xBB -#define ST7789_LCMCTRL 0xC0 -#define ST7789_VDVVRHEN 0xC2 -#define ST7789_VRHS 0xC3 -#define ST7789_VDVSET 0xC4 -#define ST7789_FRCTR2 0xC6 -#define ST7789_PWCTRL1 0xD0 -#define ST7789_PVGAMCTRL 0xE0 -#define ST7789_NVGAMCTRL 0xE1 - -// rotation -#define ST7789_MADCTL_MY 0x80 -#define ST7789_MADCTL_MX 0x40 -#define ST7789_MADCTL_MV 0x20 -#define ST7789_MADCTL_ML 0x10 -#define ST7789_MADCTL_RGB 0x00 - -#define TFT_MAD_RGB 0x00 -#define TFT_MAD_BGR 0x08 -#define TFT_MAD_COLOR_ORDER TFT_MAD_RGB - -#include "font.c" - -static const char *TAG = "st7789_display_driver"; - -static void send_message(term pid, term message, GlobalContext *global); - -static inline void delay(int ms) -{ - vTaskDelay(ms / portTICK_PERIOD_MS); -} - -struct SPI -{ - struct SPIDisplay spi_disp; - int dc_gpio; - int reset_gpio; - - avm_int_t rotation; - - Context *ctx; -}; - -// This struct is just for compatibility reasons with the SDL display driver -// so it is possible to easily copy & paste code from there. -struct Screen -{ - int w; - int h; - avm_int_t x_offset; - avm_int_t y_offset; - uint16_t *pixels; - uint16_t *pixels_out; -}; - -static struct Screen *screen; - -// This functions is taken from: -// https://stackoverflow.com/questions/18937701/combining-two-16-bits-rgb-colors-with-alpha-blending -static inline uint16_t alpha_blend_rgb565(uint32_t fg, uint32_t bg, uint8_t alpha) -{ - alpha = (alpha + 4) >> 3; - bg = (bg | (bg << 16)) & 0b00000111111000001111100000011111; - fg = (fg | (fg << 16)) & 0b00000111111000001111100000011111; - uint32_t result = ((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111; - return (uint16_t)((result >> 16) | result); -} - -static inline uint8_t rgba8888_get_alpha(uint32_t color) -{ - return color & 0xFF; -} - -static inline uint16_t rgba8888_color_to_rgb565(struct Screen *s, uint32_t color) -{ - uint8_t r = color >> 24; - uint8_t g = (color >> 16) & 0xFF; - uint8_t b = (color >> 8) & 0xFF; - - return (((uint16_t)(r >> 3)) << 11) | (((uint16_t)(g >> 2)) << 5) | ((uint16_t) b >> 3); -} - -static inline uint16_t rgb565_color_to_surface(struct Screen *s, uint16_t color16) -{ - return (uint16_t) SPI_SWAP_DATA_TX(color16, 16); -} - -static inline uint16_t uint32_color_to_surface(struct Screen *s, uint32_t color) -{ - uint16_t color16 = rgba8888_color_to_rgb565(s, color); - - return rgb565_color_to_surface(s, color16); -} - -struct PendingReply -{ - uint64_t pending_call_ref_ticks; - term pending_call_pid; -}; - -static QueueHandle_t display_messages_queue; - -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx); -static void display_init(Context *ctx, term opts); -static void display_init_alt_gamma_2(struct SPI *spi); -static void display_init_std(struct SPI *spi); -static void display_init_using_list(struct SPI *spi, term init_list); - -static inline void writedata(struct SPI *spi, uint32_t data) -{ - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - spi_display_write(&spi->spi_disp, 8, data); - spi_device_release_bus(spi->spi_disp.handle); -} - -static inline void writecommand(struct SPI *spi, uint8_t command) -{ - gpio_set_level(spi->dc_gpio, 0); - writedata(spi, command); - gpio_set_level(spi->dc_gpio, 1); -} - -static inline void set_screen_paint_area(struct SPI *spi, int x, int y, int width, int height) -{ - x += screen->x_offset; - y += screen->y_offset; - - writecommand(spi, ST7789_CASET); - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - spi_display_write(&spi->spi_disp, 32, (x << 16) | ((x + width) - 1)); - spi_device_release_bus(spi->spi_disp.handle); - - writecommand(spi, ST7789_RASET); - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - spi_display_write(&spi->spi_disp, 32, (y << 16) | ((y + height) - 1)); - spi_device_release_bus(spi->spi_disp.handle); -} - -static int draw_image_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - - uint16_t bgcolor = 0; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor = rgba8888_color_to_rgb565(screen, item->brcolor); - visible_bg = true; - } else { - visible_bg = false; - } - - int width = item->width; - const char *data = item->data.image_data.pix; - - int drawn_pixels = 0; - - uint32_t *pixels = ((uint32_t *) data) + (ypos - y) * width + (xpos - x); - uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - uint32_t img_pixel = READ_32_UNALIGNED(pixels); - uint8_t alpha = rgba8888_get_alpha(img_pixel); - if (alpha == 0xFF) { - uint16_t color = uint32_color_to_surface(screen, img_pixel); - pixmem16[drawn_pixels] = color; - } else if (visible_bg) { - uint16_t color = rgba8888_color_to_rgb565(screen, img_pixel); - uint16_t blended = alpha_blend_rgb565(color, bgcolor, alpha); - pixmem16[drawn_pixels] = rgb565_color_to_surface(screen, blended); - } else { - return drawn_pixels; - } - drawn_pixels++; - pixels++; - } - - return drawn_pixels; -} - -static int draw_scaled_cropped_img_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - - uint16_t bgcolor = 0; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor = rgba8888_color_to_rgb565(screen, item->brcolor); - visible_bg = true; - } else { - visible_bg = false; - } - - int width = item->width; - const char *data = item->data.image_data_with_size.pix; - - int drawn_pixels = 0; - - int y_scale = item->y_scale; - int x_scale = item->x_scale; - int img_width = item->data.image_data_with_size.width; - - int source_x = item->source_x; - int source_y = item->source_y; - - uint32_t *pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + ((xpos - x) / x_scale); - uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (source_x + (width / x_scale) > img_width) { - width = (img_width - source_x) * x_scale; - } - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - uint32_t img_pixel = READ_32_UNALIGNED(pixels); - uint8_t alpha = rgba8888_get_alpha(img_pixel); - if (alpha == 0xFF) { - uint16_t color = uint32_color_to_surface(screen, img_pixel); - pixmem16[drawn_pixels] = color; - } else if (visible_bg) { - uint16_t color = rgba8888_color_to_rgb565(screen, img_pixel); - uint16_t blended = alpha_blend_rgb565(color, bgcolor, alpha); - pixmem16[drawn_pixels] = rgb565_color_to_surface(screen, blended); - } else { - return drawn_pixels; - } - drawn_pixels++; - // TODO: optimize here - pixels = ((uint32_t *) data) + (source_y + ((ypos - y) / y_scale)) * img_width + source_x + (j / x_scale); - } - - return drawn_pixels; -} - -static int draw_rect_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int width = item->width; - uint16_t color = uint32_color_to_surface(screen, item->brcolor); - - int drawn_pixels = 0; - - uint16_t *pixmem16 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - pixmem16[drawn_pixels] = color; - drawn_pixels++; - } - - return drawn_pixels; -} - -static int draw_text_x(int xpos, int ypos, int max_line_len, BaseDisplayItem *item) -{ - int x = item->x; - int y = item->y; - uint16_t fgcolor = uint32_color_to_surface(screen, item->data.text_data.fgcolor); - uint16_t bgcolor; - bool visible_bg; - if (item->brcolor != 0) { - bgcolor = uint32_color_to_surface(screen, item->brcolor); - visible_bg = true; - } else { - visible_bg = false; - } - - char *text = (char *) item->data.text_data.text; - - int width = item->width; - - int drawn_pixels = 0; - - uint16_t *pixmem32 = (uint16_t *) (((uint8_t *) screen->pixels) + xpos * sizeof(uint16_t)); - - if (width > xpos - x + max_line_len) { - width = xpos - x + max_line_len; - } - - for (int j = xpos - x; j < width; j++) { - int char_index = j / CHAR_WIDTH; - char c = text[char_index]; - unsigned const char *glyph = fontdata + ((unsigned char) c) * 16; - - unsigned char row = glyph[ypos - y]; - - bool opaque; - int k = j % CHAR_WIDTH; - if (row & (1 << (7 - k))) { - opaque = true; - } else { - opaque = false; - } - - if (opaque) { - pixmem32[drawn_pixels] = fgcolor; - } else if (visible_bg) { - pixmem32[drawn_pixels] = bgcolor; - } else { - return drawn_pixels; - } - drawn_pixels++; - } - - return drawn_pixels; -} - -static int find_max_line_len(BaseDisplayItem *items, int count, int xpos, int ypos) -{ - int line_len = screen->w - xpos; - - for (int i = 0; i < count; i++) { - BaseDisplayItem *item = &items[i]; - - if ((xpos < item->x) && (ypos >= item->y) && (ypos < item->y + item->height)) { - int len_to_item = item->x - xpos; - line_len = (line_len > len_to_item) ? len_to_item : line_len; - } - } - - return line_len; -} - -static int draw_x(int xpos, int ypos, BaseDisplayItem *items, int items_count) -{ - bool below = false; - - for (int i = 0; i < items_count; i++) { - BaseDisplayItem *item = &items[i]; - if ((xpos < item->x) || (xpos >= item->x + item->width) || (ypos < item->y) || (ypos >= item->y + item->height)) { - continue; - } - - int max_line_len = below ? 1 : find_max_line_len(items, i, xpos, ypos); - - int drawn_pixels = 0; - switch (items[i].primitive) { - case Image: - drawn_pixels = draw_image_x(xpos, ypos, max_line_len, item); - break; - - case Rect: - drawn_pixels = draw_rect_x(xpos, ypos, max_line_len, item); - break; - - case ScaledCroppedImage: - drawn_pixels = draw_scaled_cropped_img_x(xpos, ypos, max_line_len, item); - break; - - case Text: - drawn_pixels = draw_text_x(xpos, ypos, max_line_len, item); - break; - default: { - fprintf(stderr, "unexpected display list command.\n"); - } - } - - if (drawn_pixels != 0) { - return drawn_pixels; - } - - below = true; - } - - return 1; -} - -static void do_update(Context *ctx, term display_list) -{ - int proper; - int len = term_list_length(display_list, &proper); - - BaseDisplayItem *items = malloc(sizeof(BaseDisplayItem) * len); - - term t = display_list; - for (int i = 0; i < len; i++) { - init_item(&items[i], term_get_list_head(t), ctx); - t = term_get_list_tail(t); - } - - int screen_width = screen->w; - int screen_height = screen->h; - struct SPI *spi = ctx->platform_data; - - set_screen_paint_area(spi, 0, 0, screen_width, screen_height); - writecommand(spi, ST7789_RAMWR); - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - - bool transaction_in_progress = false; - - for (int ypos = 0; ypos < screen_height; ypos++) { - int xpos = 0; - while (xpos < screen_width) { - int drawn_pixels = draw_x(xpos, ypos, items, len); - xpos += drawn_pixels; - } - - if (transaction_in_progress) { - spi_transaction_t *trans; - // I did a quick measurement, and most of the time is spent waiting for DMA transaction - // eg. 23 us spent in draw_x, 188 us spent in spi_device_get_trans_result - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); - } - - // NEW CODE - void *tmp = screen->pixels; - screen->pixels = screen->pixels_out; - screen->pixels_out = tmp; - spi_display_dmawrite(&spi->spi_disp, screen_width * sizeof(uint16_t), screen->pixels_out); - transaction_in_progress = true; - } - - if (transaction_in_progress) { - spi_transaction_t *trans; - spi_device_get_trans_result(spi->spi_disp.handle, &trans, portMAX_DELAY); - } - - spi_device_release_bus(spi->spi_disp.handle); - - destroy_items(items, len); -} - -static void draw_buffer(struct SPI *spi, int x, int y, int width, int height, const void *imgdata) -{ - const uint16_t *data = imgdata; - - set_screen_paint_area(spi, x, y, width, height); - - writecommand(spi, ST7789_RAMWR); - - int dest_size = width * height; - int buf_pixel_size = (dest_size > 1024) ? 1024 : dest_size; - - int chunks = dest_size / 1024; - - uint16_t *tmpbuf = heap_caps_malloc(buf_pixel_size * sizeof(uint16_t), MALLOC_CAP_DMA); - - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - for (int i = 0; i < chunks; i++) { - const uint16_t *data_b = data + 1024 * i; - for (int j = 0; j < 1024; j++) { - tmpbuf[j] = SPI_SWAP_DATA_TX(data_b[j], 16); - } - spi_display_dmawrite(&spi->spi_disp, buf_pixel_size * sizeof(uint16_t), tmpbuf); - } - int last_chunk_size = dest_size - chunks * 1024; - if (last_chunk_size) { - const uint16_t *data_b = data + chunks * 1024; - for (int j = 0; j < 1024; j++) { - tmpbuf[j] = SPI_SWAP_DATA_TX(data_b[j], 16); - } - spi_display_dmawrite(&spi->spi_disp, last_chunk_size * sizeof(uint16_t), tmpbuf); - } - spi_device_release_bus(spi->spi_disp.handle); - - free(tmpbuf); -} - -static void process_message(Message *message, Context *ctx) -{ - GenMessage gen_message; - if (UNLIKELY(port_parse_gen_message(message->message, &gen_message) != GenCallMessage)) { - fprintf(stderr, "Received invalid message."); - AVM_ABORT(); - } - - term req = gen_message.req; - if (UNLIKELY(!term_is_tuple(req) || term_get_tuple_arity(req) < 1)) { - AVM_ABORT(); - } - term cmd = term_get_tuple_element(req, 0); - - struct SPI *spi = ctx->platform_data; - - if (cmd == context_make_atom(ctx, "\x6" - "update")) { - term display_list = term_get_tuple_element(req, 1); - do_update(ctx, display_list); - - } else if (cmd == context_make_atom(ctx, "\xB" - "draw_buffer")) { - int x = term_to_int(term_get_tuple_element(req, 1)); - int y = term_to_int(term_get_tuple_element(req, 2)); - int width = term_to_int(term_get_tuple_element(req, 3)); - int height = term_to_int(term_get_tuple_element(req, 4)); - unsigned long addr_low = term_to_int(term_get_tuple_element(req, 5)); - unsigned long addr_high = term_to_int(term_get_tuple_element(req, 6)); - - const void *data = (const void *) ((addr_low | (addr_high << 16))); - - draw_buffer(spi, x, y, width, height, data); - - // draw_buffer is a kind of cast, no need to reply - return; - - } else if (cmd == globalcontext_make_atom(ctx->global, "\xA" "load_image")) { - handle_load_image(req, gen_message.ref, gen_message.pid, ctx); - return; - - } else { - fprintf(stderr, "display: "); - term_display(stderr, req, ctx); - fprintf(stderr, "\n"); - } - - BEGIN_WITH_STACK_HEAP(TUPLE_SIZE(2) + REF_SIZE, heap); - term return_tuple = term_alloc_tuple(2, &heap); - term_put_tuple_element(return_tuple, 0, gen_message.ref); - term_put_tuple_element(return_tuple, 1, OK_ATOM); - - send_message(gen_message.pid, return_tuple, ctx->global); - END_WITH_STACK_HEAP(heap, ctx->global); -} - -static void process_messages(void *arg) -{ - struct SPI *args = arg; - - while (true) { - Message *message; - xQueueReceive(display_messages_queue, &message, portMAX_DELAY); - process_message(message, args->ctx); - - BEGIN_WITH_STACK_HEAP(1, temp_heap); - mailbox_message_dispose(&message->base, &temp_heap); - END_WITH_STACK_HEAP(temp_heap, args->ctx->global); - } -} - -static NativeHandlerResult display_driver_consume_mailbox(Context *ctx) -{ - MailboxMessage *mbox_msg = mailbox_take_message(&ctx->mailbox); - Message *msg = CONTAINER_OF(mbox_msg, Message, base); - - xQueueSend(display_messages_queue, &msg, 1); - - return NativeContinue; -} - -static void set_rotation(struct SPI *spi, int rotation) -{ - if (rotation == 1) { - writecommand(spi, ST7789_MADCTL); - writedata(spi, ST7789_MADCTL_MX | ST7789_MADCTL_MV | ST7789_MADCTL_RGB); - } -} - -Context *st7789_display_create_port(GlobalContext *global, term opts) -{ - Context *ctx = context_new(global); - ctx->native_handler = display_driver_consume_mailbox; - display_init(ctx, opts); - return ctx; -} - -static void send_message(term pid, term message, GlobalContext *global) -{ - int local_process_id = term_to_local_process_id(pid); - globalcontext_send_message(global, local_process_id, message); -} - -static void display_init(Context *ctx, term opts) -{ - term width_term = interop_kv_get_value_default( - opts, ATOM_STR("\x5", "width"), term_from_int(320), ctx->global); - term height_term = interop_kv_get_value_default( - opts, ATOM_STR("\x6", "height"), term_from_int(240), ctx->global); - - screen = malloc(sizeof(struct Screen)); - screen->w = term_to_int(width_term); - screen->h = term_to_int(height_term); - screen->pixels = heap_caps_malloc(screen->w * sizeof(uint16_t), MALLOC_CAP_DMA); - screen->pixels_out = heap_caps_malloc(screen->w * sizeof(uint16_t), MALLOC_CAP_DMA); - - display_messages_queue = xQueueCreate(32, sizeof(Message *)); - - struct SPI *spi = malloc(sizeof(struct SPI)); - ctx->platform_data = spi; - - spi->ctx = ctx; - - struct SPIDisplayConfig spi_config; - spi_display_init_config(&spi_config); - spi_config.mode = SPI_MODE; - spi_config.clock_speed_hz = SPI_CLOCK_HZ; - spi_display_parse_config(&spi_config, opts, ctx->global); - spi_display_init(&spi->spi_disp, &spi_config); - - bool ok = display_common_gpio_from_opts(opts, ATOM_STR("\x2", "dc"), &spi->dc_gpio, ctx->global); - - bool reset_configured = true; - if (!display_common_gpio_from_opts(opts, ATOM_STR("\x5", "reset"), &spi->reset_gpio, ctx->global)) { - ESP_LOGI(TAG, "Reset GPIO not configured."); - reset_configured = false; - } - - term rotation = interop_kv_get_value_default(opts, ATOM_STR("\x8", "rotation"), term_from_int(0), ctx->global); - ok = ok && term_is_integer(rotation); - spi->rotation = term_to_int(rotation); - - term invon = interop_kv_get_value_default(opts, ATOM_STR("\x10", "enable_tft_invon"), FALSE_ATOM, ctx->global); - ok = ok && ((invon == TRUE_ATOM) || (invon == FALSE_ATOM)); - bool enable_tft_invon = (invon == TRUE_ATOM); - - term x_off_term = interop_kv_get_value_default( - opts, ATOM_STR("\x8", "x_offset"), term_from_int(0), ctx->global); - term y_off_term = interop_kv_get_value_default( - opts, ATOM_STR("\x8", "y_offset"), term_from_int(0), ctx->global); - - if (term_is_integer(x_off_term) && term_is_integer(y_off_term)) { - screen->x_offset = term_to_int(x_off_term); - screen->y_offset = term_to_int(y_off_term); - } else { - ok = false; - } - - if (UNLIKELY(!ok)) { - ESP_LOGE(TAG, "Failed init: invalid display parameters."); - return; - } - - // Reset - if (reset_configured) { - spi_device_acquire_bus(spi->spi_disp.handle, portMAX_DELAY); - gpio_set_direction(spi->reset_gpio, GPIO_MODE_OUTPUT); - gpio_set_level(spi->reset_gpio, 1); - vTaskDelay(50 / portTICK_PERIOD_MS); - gpio_set_level(spi->reset_gpio, 0); - vTaskDelay(50 / portTICK_PERIOD_MS); - gpio_set_level(spi->reset_gpio, 1); - spi_device_release_bus(spi->spi_disp.handle); - } - - gpio_set_direction(spi->dc_gpio, GPIO_MODE_OUTPUT); - - if (!reset_configured) { - writecommand(spi, ST7789_SWRESET); - delay(100); - } - - term maybe_init_list - = interop_kv_get_value_default(opts, ATOM_STR("\x9", "init_list"), term_nil(), ctx->global); - if (maybe_init_list != term_nil()) { - display_init_using_list(spi, maybe_init_list); - } else { - term init_seq_type_term = interop_kv_get_value_default(opts, ATOM_STR("\xD", "init_seq_type"), term_nil(), ctx->global); - int str_ok; - char *init_seq_type_string = interop_term_to_string(init_seq_type_term, &str_ok); - if (str_ok && !strcmp(init_seq_type_string, "alt_gamma_2")) { - display_init_alt_gamma_2(spi); - free(init_seq_type_string); - } else { - display_init_std(spi); - } - - set_rotation(spi, spi->rotation); - - if (enable_tft_invon) { - writecommand(spi, ST7789_INVON); - } - } - - writecommand(spi, ST7789_DISPON); - delay(120); - - struct BacklightGPIOConfig backlight_config; - backlight_gpio_init_config(&backlight_config); - backlight_gpio_parse_config(&backlight_config, opts, ctx->global); - backlight_gpio_init(&backlight_config); - - xTaskCreate(process_messages, "display", 10000, spi, 1, NULL); -} - -static void display_init_alt_gamma_2(struct SPI *spi) -{ - writecommand(spi, ST7789_SLPOUT); - delay(120); - - writecommand(spi, ST7789_NORON); - - // - display and color format setting - // - writecommand(spi, ST7789_MADCTL); - writedata(spi, TFT_MAD_COLOR_ORDER); - - writecommand(spi, ST7789_COLMOD); - writedata(spi, 0x55); - delay(10); - - // - ST7789V frame rate setting - // - writecommand(spi, ST7789_PORCTRL); - writedata(spi, 0x0C); - writedata(spi, 0x0C); - writedata(spi, 0x00); - writedata(spi, 0x33); - writedata(spi, 0x33); - - writecommand(spi, ST7789_GCTRL); - writedata(spi, 0x75); - - // - ST7789V power setting - // - writecommand(spi, ST7789_VCOMS); - writedata(spi, 0x1A); - - writecommand(spi, ST7789_LCMCTRL); - writedata(spi, 0x2C); - - writecommand(spi, ST7789_VDVVRHEN); - writedata(spi, 0x01); - - writecommand(spi, ST7789_VRHS); - writedata(spi, 0x13); - - writecommand(spi, ST7789_VDVSET); - writedata(spi, 0x20); - - writecommand(spi, ST7789_FRCTR2); - writedata(spi, 0x0F); - - writecommand(spi, ST7789_PWCTRL1); - writedata(spi, 0xA4); - writedata(spi, 0xA1); - - // - ST7789V gamma setting - // - writecommand(spi, ST7789_PVGAMCTRL); - writedata(spi, 0xD0); - writedata(spi, 0x0D); - writedata(spi, 0x14); - writedata(spi, 0x0D); - writedata(spi, 0x0D); - writedata(spi, 0x09); - writedata(spi, 0x38); - writedata(spi, 0x44); - writedata(spi, 0x4E); - writedata(spi, 0x3A); - writedata(spi, 0x17); - writedata(spi, 0x18); - writedata(spi, 0x2F); - writedata(spi, 0x30); - - writecommand(spi, ST7789_NVGAMCTRL); - writedata(spi, 0xD0); - writedata(spi, 0x09); - writedata(spi, 0x0F); - writedata(spi, 0x08); - writedata(spi, 0x07); - writedata(spi, 0x14); - writedata(spi, 0x37); - writedata(spi, 0x44); - writedata(spi, 0x4D); - writedata(spi, 0x38); - writedata(spi, 0x15); - writedata(spi, 0x16); - writedata(spi, 0x2C); - writedata(spi, 0x3E); - - writecommand(spi, ST7789_CASET); - writedata(spi, 0x00); - writedata(spi, 0x00); - writedata(spi, 0x00); - writedata(spi, 0xEF); // 239 - - writecommand(spi, ST7789_RASET); - writedata(spi, 0x00); - writedata(spi, 0x00); - writedata(spi, 0x01); - writedata(spi, 0x3F); // 319 -} - -static void display_init_std(struct SPI *spi) -{ - writecommand(spi, ST7789_SLPOUT); - delay(120); - - writecommand(spi, ST7789_NORON); - - // - display and color format setting - // - writecommand(spi, ST7789_MADCTL); - writedata(spi, TFT_MAD_COLOR_ORDER); - - writecommand(spi, 0xB6); - writedata(spi, 0x0A); - writedata(spi, 0x82); - - writecommand(spi, ST7789_RAMCTRL); - writedata(spi, 0x00); - writedata(spi, 0xE0); - - writecommand(spi, ST7789_COLMOD); - writedata(spi, 0x55); - delay(10); - - // - ST7789V frame rate setting - // - writecommand(spi, ST7789_PORCTRL); - writedata(spi, 0x0C); - writedata(spi, 0x0C); - writedata(spi, 0x00); - writedata(spi, 0x33); - writedata(spi, 0x33); - - writecommand(spi, ST7789_GCTRL); - writedata(spi, 0x35); - - // - ST7789V power setting - // - writecommand(spi, ST7789_VCOMS); - writedata(spi, 0x28); - - writecommand(spi, ST7789_LCMCTRL); - writedata(spi, 0x0C); - - writecommand(spi, ST7789_VDVVRHEN); - writedata(spi, 0x01); - writedata(spi, 0xFF); - - writecommand(spi, ST7789_VRHS); - writedata(spi, 0x10); - - writecommand(spi, ST7789_VDVSET); - writedata(spi, 0x20); - - writecommand(spi, ST7789_FRCTR2); - writedata(spi, 0x0F); - - writecommand(spi, ST7789_PWCTRL1); - writedata(spi, 0xA4); - writedata(spi, 0xA1); - - // - ST7789V gamma setting - // - writecommand(spi, ST7789_PVGAMCTRL); - writedata(spi, 0xD0); - writedata(spi, 0x00); - writedata(spi, 0x02); - writedata(spi, 0x07); - writedata(spi, 0x0A); - writedata(spi, 0x28); - writedata(spi, 0x32); - writedata(spi, 0x44); - writedata(spi, 0x42); - writedata(spi, 0x06); - writedata(spi, 0x0E); - writedata(spi, 0x12); - writedata(spi, 0x14); - writedata(spi, 0x17); - - writecommand(spi, ST7789_NVGAMCTRL); - writedata(spi, 0xD0); - writedata(spi, 0x00); - writedata(spi, 0x02); - writedata(spi, 0x07); - writedata(spi, 0x0A); - writedata(spi, 0x28); - writedata(spi, 0x31); - writedata(spi, 0x54); - writedata(spi, 0x47); - writedata(spi, 0x0E); - writedata(spi, 0x1C); - writedata(spi, 0x17); - writedata(spi, 0x1B); - writedata(spi, 0x1E); - - writecommand(spi, ST7789_CASET); - writedata(spi, 0x00); - writedata(spi, 0x00); - writedata(spi, 0x00); - writedata(spi, 0xEF); // 239 - - writecommand(spi, ST7789_RASET); - writedata(spi, 0x00); - writedata(spi, 0x00); - writedata(spi, 0x01); - writedata(spi, 0x3F); // 319 -} - -static void writecmddata(struct SPI *spi, uint8_t cmd, const uint8_t *data, size_t length) -{ - writecommand(spi, cmd); - for (int i = 0; i < length; i++) { - writedata(spi, data[i]); - } -} - -static void display_init_using_list(struct SPI *spi, term init_list) -{ - term t = init_list; - while (term_is_nonempty_list(t)) { - term head = term_get_list_head(t); - if (term_is_tuple(head) && term_get_tuple_arity(head) == 2) { - term cmd_term = term_get_tuple_element(head, 0); - term data_term = term_get_tuple_element(head, 1); - if (term_is_integer(cmd_term) && term_is_binary(data_term)) { - avm_int_t cmd = term_to_int(cmd_term); - const uint8_t *data = (const uint8_t *) term_binary_data(data_term); - writecmddata(spi, cmd, data, term_binary_size(data_term)); - } else if ((cmd_term == context_make_atom(spi->ctx, ATOM_STR("\x8", "sleep_ms"))) - && term_is_integer(data_term)) { - delay(term_to_int(data_term)); - } else { - // invalid - break; - } - } else { - // invalid - break; - } - - t = term_get_list_tail(t); - } - if (t != term_nil()) { - fprintf(stderr, "Invalid init_list!\n"); - } -} diff --git a/sdl_display/ufontlib.c b/ufontlib.c similarity index 97% rename from sdl_display/ufontlib.c rename to ufontlib.c index 6569865..82dea12 100644 --- a/sdl_display/ufontlib.c +++ b/ufontlib.c @@ -25,7 +25,9 @@ #ifdef WITH_ZLIB #include #else +#ifndef ESP_PLATFORM #include "miniz.c" +#endif #include "miniz.h" #endif #include @@ -587,12 +589,19 @@ static uint32_t ufont_iff_align(uint32_t size) static int ufont_iff_is_valid_ufl(const void *iff) { - return memcmp(iff, "UFL0", 4) == 0; + // Standard IFF layout produced by fontconvert.py: + // offset 0: "FORM" (IFF group magic) + // offset 4: file size (big-endian) + // offset 8: "uFL0" (form type, lowercase u) + // offset 12+: IFF records (uFH0, uFP0, uFI0, uFB0) + const uint8_t *data = iff; + return memcmp(data, "FORM", 4) == 0 + && memcmp(data + 8, "uFL0", 4) == 0; } EpdFont *ufont_parse(const void *iff_binary, int buf_size) { - if (ufont_iff_is_valid_ufl(iff_binary)) { + if (!ufont_iff_is_valid_ufl(iff_binary)) { return NULL; } diff --git a/sdl_display/ufontlib.h b/ufontlib.h similarity index 100% rename from sdl_display/ufontlib.h rename to ufontlib.h