diff --git a/src/Makefile b/src/Makefile index 82a581bbc..b3796372d 100755 --- a/src/Makefile +++ b/src/Makefile @@ -173,9 +173,9 @@ MPY_CFLAGS += -DMICROPY_PY_MICRO_SPEECH=1 MICROPY_ARGS += MICROPY_PY_MICRO_SPEECH=1 endif -ifeq ($(MICROPY_PY_LCD), 1) -MPY_CFLAGS += -DMICROPY_PY_LCD=1 -MICROPY_ARGS += MICROPY_PY_LCD=1 +ifeq ($(MICROPY_PY_DISPLAY), 1) +MPY_CFLAGS += -DMICROPY_PY_DISPLAY=1 +MICROPY_ARGS += MICROPY_PY_DISPLAY=1 endif ifeq ($(MICROPY_PY_TV), 1) diff --git a/src/omv/modules/py_display.c b/src/omv/modules/py_display.c new file mode 100644 index 000000000..e785c022a --- /dev/null +++ b/src/omv/modules/py_display.c @@ -0,0 +1,246 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2023 Ibrahim Abdelkader + * Copyright (c) 2013-2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Display Python module. + */ +#include "omv_boardconfig.h" + +#if MICROPY_PY_DISPLAY + +#include "py/obj.h" +#include "py/objarray.h" +#include "py/mphal.h" +#include "py/runtime.h" + +#include "py_helper.h" +#include "py_image.h" +#include "py_display.h" + +STATIC mp_obj_t py_display_width(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->width); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_width_obj, py_display_width); + +STATIC mp_obj_t py_display_height(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->height); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_height_obj, py_display_height); + +STATIC mp_obj_t py_display_triple_buffer(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->triple_buffer); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_triple_buffer_obj, py_display_triple_buffer); + +STATIC mp_obj_t py_display_bgr(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->bgr); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_bgr_obj, py_display_bgr); + +STATIC mp_obj_t py_display_byte_swap(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->byte_swap); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_byte_swap_obj, py_display_byte_swap); + +STATIC mp_obj_t py_display_framesize(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->framesize); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_framesize_obj, py_display_framesize); + +STATIC mp_obj_t py_display_refresh(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_int(self->refresh); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_refresh_obj, py_display_refresh); + +STATIC mp_obj_t py_display_deinit(mp_obj_t self_in) { + py_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + if (display_p->deinit != NULL) { + display_p->deinit(self); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_display_deinit_obj, py_display_deinit); + +STATIC mp_obj_t py_display_clear(uint n_args, const mp_obj_t *args) { + py_display_obj_t *self = MP_OBJ_TO_PTR(args[0]); + bool display_off = (n_args > 1 && mp_obj_get_int(args[1])); + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + if (display_p->clear != NULL) { + display_p->clear(self, display_off); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_display_clear_obj, 1, 2, py_display_clear); + +STATIC mp_obj_t py_display_backlight(uint n_args, const mp_obj_t *args) { + py_display_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args > 1) { + uint32_t intensity = mp_obj_get_int(args[1]); + if ((intensity < 0) || (255 < intensity)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= intensity <= 255!")); + } + + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + if (display_p->set_backlight != NULL) { + display_p->set_backlight(self, intensity); + } + } else { + return mp_obj_new_int(self->intensity); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_display_backlight_obj, 1, 2, py_display_backlight); + +STATIC mp_obj_t py_display_write(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { + py_display_obj_t *self = MP_OBJ_TO_PTR(args[0]); + image_t *arg_img = py_image_cobj(args[1]); + + int arg_x_off = 0; + py_helper_keyword_int_maybe(n_args, args, 2, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_x), &arg_x_off); + + int arg_y_off = 0; + py_helper_keyword_int_maybe(n_args, args, 3, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_y), &arg_y_off); + + float arg_x_scale = 1.f; + bool got_x_scale = py_helper_keyword_float_maybe(n_args, args, 4, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_x_scale), &arg_x_scale); + + float arg_y_scale = 1.f; + bool got_y_scale = py_helper_keyword_float_maybe(n_args, args, 5, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_y_scale), &arg_y_scale); + + rectangle_t arg_roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 6, kw_args, &arg_roi); + + int arg_rgb_channel = py_helper_keyword_int(n_args, args, 7, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_rgb_channel), -1); + if ((arg_rgb_channel < -1) || (2 < arg_rgb_channel)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("-1 <= rgb_channel <= 2!")); + } + + int arg_alpha = py_helper_keyword_int(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha), 256); + if ((arg_alpha < 0) || (256 < arg_alpha)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= alpha <= 256!")); + } + + const uint16_t *color_palette = py_helper_keyword_color_palette(n_args, args, 7, kw_args, NULL); + const uint8_t *alpha_palette = py_helper_keyword_alpha_palette(n_args, args, 8, kw_args, NULL); + image_hint_t hint = py_helper_keyword_int(n_args, args, 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hint), 0); + + float arg_x_size; + bool got_x_size = py_helper_keyword_float_maybe(n_args, args, 10, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_x_size), &arg_x_size); + + float arg_y_size; + bool got_y_size = py_helper_keyword_float_maybe(n_args, args, 11, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_y_size), &arg_y_size); + + if (got_x_scale && got_x_size) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either x_scale or x_size not both!")); + } + if (got_y_scale && got_y_size) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either y_scale or y_size not both!")); + } + + if (got_x_size) { + arg_x_scale = arg_x_size / arg_roi.w; + } + if (got_y_size) { + arg_y_scale = arg_y_size / arg_roi.h; + } + + if ((!got_x_scale) && (!got_x_size) && got_y_size) { + arg_x_scale = arg_y_scale; + } + if ((!got_y_scale) && (!got_y_size) && got_x_size) { + arg_y_scale = arg_x_scale; + } + + if ((!self->triple_buffer) && (arg_y_scale < 0)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Vertical flip requires triple buffering!")); + } + + fb_alloc_mark(); + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + display_p->write(self, arg_img, arg_x_off, arg_y_off, arg_x_scale, arg_y_scale, &arg_roi, + arg_rgb_channel, arg_alpha, color_palette, alpha_palette, hint); + fb_alloc_free_till_mark(); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_display_write_obj, 2, py_display_write); + +STATIC const mp_rom_map_elem_t py_display_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_display) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&py_display_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_display_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_display_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_triple_buffer), MP_ROM_PTR(&py_display_triple_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_bgr), MP_ROM_PTR(&py_display_bgr_obj) }, + { MP_ROM_QSTR(MP_QSTR_byte_swap), MP_ROM_PTR(&py_display_byte_swap_obj) }, + { MP_ROM_QSTR(MP_QSTR_framesize), MP_ROM_PTR(&py_display_framesize_obj) }, + { MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&py_display_refresh_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_display_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_backlight), MP_ROM_PTR(&py_display_backlight_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&py_display_write_obj) }, +}; +MP_DEFINE_CONST_DICT(py_display_locals_dict, py_display_locals_dict_table); + +STATIC const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_display) }, + { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT(DISPLAY_RESOLUTION_QVGA) }, + { MP_ROM_QSTR(MP_QSTR_TQVGA), MP_ROM_INT(DISPLAY_RESOLUTION_TQVGA) }, + { MP_ROM_QSTR(MP_QSTR_FHVGA), MP_ROM_INT(DISPLAY_RESOLUTION_FHVGA) }, + { MP_ROM_QSTR(MP_QSTR_FHVGA2), MP_ROM_INT(DISPLAY_RESOLUTION_FHVGA2) }, + { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT(DISPLAY_RESOLUTION_VGA) }, + { MP_ROM_QSTR(MP_QSTR_THVGA), MP_ROM_INT(DISPLAY_RESOLUTION_THVGA) }, + { MP_ROM_QSTR(MP_QSTR_FWVGA), MP_ROM_INT(DISPLAY_RESOLUTION_FWVGA) }, + { MP_ROM_QSTR(MP_QSTR_FWVGA2), MP_ROM_INT(DISPLAY_RESOLUTION_FWVGA2) }, + { MP_ROM_QSTR(MP_QSTR_TFWVGA), MP_ROM_INT(DISPLAY_RESOLUTION_TFWVGA) }, + { MP_ROM_QSTR(MP_QSTR_TFWVGA2), MP_ROM_INT(DISPLAY_RESOLUTION_TFWVGA2) }, + { MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT(DISPLAY_RESOLUTION_SVGA) }, + { MP_ROM_QSTR(MP_QSTR_WSVGA), MP_ROM_INT(DISPLAY_RESOLUTION_WSVGA) }, + { MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT(DISPLAY_RESOLUTION_XGA) }, + { MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT(DISPLAY_RESOLUTION_SXGA) }, + { MP_ROM_QSTR(MP_QSTR_SXGA2), MP_ROM_INT(DISPLAY_RESOLUTION_SXGA2) }, + { MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT(DISPLAY_RESOLUTION_UXGA) }, + { MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT(DISPLAY_RESOLUTION_HD) }, + { MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT(DISPLAY_RESOLUTION_FHD) }, + + #ifdef OMV_SPI_DISPLAY_CONTROLLER + { MP_ROM_QSTR(MP_QSTR_SPIDisplay), MP_ROM_PTR(&py_spi_display_type) }, + #endif + #ifdef OMV_RGB_DISPLAY_CONTROLLER + { MP_ROM_QSTR(MP_QSTR_RGBDisplay), MP_ROM_PTR(&py_rgb_display_type) }, + #endif + #ifdef OMV_DSI_DISPLAY_CONTROLLER + { MP_ROM_QSTR(MP_QSTR_DSIDisplay), MP_ROM_PTR(&py_dsi_display_type) }, + #endif + #if OMV_DISPLAY_CEC_ENABLE || OMV_DISPLAY_DDC_ENABLE + { MP_ROM_QSTR(MP_QSTR_DisplayData), MP_ROM_PTR(&py_display_data_type) }, + #endif +}; +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t display_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict, +}; + +MP_REGISTER_MODULE(MP_QSTR_display, display_module); +#endif // MICROPY_PY_DISPLAY diff --git a/src/omv/modules/py_display.h b/src/omv/modules/py_display.h new file mode 100644 index 000000000..e9dca6102 --- /dev/null +++ b/src/omv/modules/py_display.h @@ -0,0 +1,78 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2023 Ibrahim Abdelkader + * Copyright (c) 2013-2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Display Python module. + */ +#ifndef __PY_DISPLAY_H__ +#define __PY_DISPLAY_H__ + +#include "omv_gpio.h" +#include "omv_spi.h" +#include "py_image.h" + +#define FRAMEBUFFER_COUNT 3 + +typedef enum { + DISPLAY_RESOLUTION_QVGA, + DISPLAY_RESOLUTION_TQVGA, + DISPLAY_RESOLUTION_FHVGA, + DISPLAY_RESOLUTION_FHVGA2, + DISPLAY_RESOLUTION_VGA, + DISPLAY_RESOLUTION_THVGA, + DISPLAY_RESOLUTION_FWVGA, + DISPLAY_RESOLUTION_FWVGA2, + DISPLAY_RESOLUTION_TFWVGA, + DISPLAY_RESOLUTION_TFWVGA2, + DISPLAY_RESOLUTION_SVGA, + DISPLAY_RESOLUTION_WSVGA, + DISPLAY_RESOLUTION_XGA, + DISPLAY_RESOLUTION_SXGA, + DISPLAY_RESOLUTION_SXGA2, + DISPLAY_RESOLUTION_UXGA, + DISPLAY_RESOLUTION_HD, + DISPLAY_RESOLUTION_FHD, + DISPLAY_RESOLUTION_MAX +} display_resolution_t; + +typedef struct _py_display_obj_t { + mp_obj_base_t base; + uint32_t width; + uint32_t height; + uint32_t framesize; + uint32_t refresh; + uint32_t intensity; + bool bgr; + bool byte_swap; + bool display_on; + #if defined(OMV_SPI_DISPLAY_CONTROLLER) + omv_spi_t spi_bus; + bool spi_tx_running; + uint32_t spi_baudrate; + #endif + bool triple_buffer; + uint32_t framebuffer_tail; + volatile uint32_t framebuffer_head; + uint16_t *framebuffers[FRAMEBUFFER_COUNT]; +} py_display_obj_t; + +// Display protocol +typedef struct _py_display_p_t { + void (*deinit) (py_display_obj_t *self); + void (*clear) (py_display_obj_t *self, bool display_off); + void (*write) (py_display_obj_t *self, image_t *src_img, int dst_x_start, int dst_y_start, + float x_scale, float y_scale, rectangle_t *roi, int rgb_channel, int alpha, + const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint); + void (*set_backlight) (py_display_obj_t *self, uint32_t intensity); +} py_display_p_t; + +extern const mp_obj_type_t py_spi_display_type; +extern const mp_obj_type_t py_rgb_display_type; +extern const mp_obj_type_t py_dsi_display_type; +extern const mp_obj_type_t py_display_data_type; +extern const mp_obj_dict_t py_display_locals_dict; +#endif // __PY_DISPLAY_H__ diff --git a/src/omv/modules/py_lcd.c b/src/omv/modules/py_lcd.c deleted file mode 100644 index b6723159c..000000000 --- a/src/omv/modules/py_lcd.c +++ /dev/null @@ -1,1845 +0,0 @@ -/* - * This file is part of the OpenMV project. - * - * Copyright (c) 2013-2021 Ibrahim Abdelkader - * Copyright (c) 2013-2021 Kwabena W. Agyeman - * - * This work is licensed under the MIT license, see the file LICENSE for details. - * - * LCD Python module. - */ -#include "omv_boardconfig.h" - -#if MICROPY_PY_LCD - -#include "py/obj.h" -#include "py/nlr.h" -#include "py/runtime.h" -#include "pendsv.h" - -#include "py_lcd_cec.h" -#include "py_lcd_touch.h" -#include "py_helper.h" -#include "py_image.h" -#include "extmod/machine_i2c.h" -#include "omv_gpio.h" -#include "omv_spi.h" - -#define FRAMEBUFFER_COUNT 3 -static int framebuffer_tail = 0; -static volatile int framebuffer_head = 0; -static uint16_t *framebuffers[FRAMEBUFFER_COUNT] = {}; - -static int lcd_width = 0; -static int lcd_height = 0; - -static enum { - LCD_NONE, - LCD_SHIELD, - LCD_DISPLAY, - LCD_DISPLAY_WITH_HDMI, - LCD_DISPLAY_ONLY_HDMI -} -lcd_type = LCD_NONE; - -static bool lcd_triple_buffer = false; -static bool lcd_bgr = false; -static bool lcd_byte_reverse = false; - -static enum { - LCD_DISPLAY_QVGA, - LCD_DISPLAY_TQVGA, - LCD_DISPLAY_FHVGA, - LCD_DISPLAY_FHVGA2, - LCD_DISPLAY_VGA, - LCD_DISPLAY_THVGA, - LCD_DISPLAY_FWVGA, - LCD_DISPLAY_FWVGA2, - LCD_DISPLAY_TFWVGA, - LCD_DISPLAY_TFWVGA2, - LCD_DISPLAY_SVGA, - LCD_DISPLAY_WSVGA, - LCD_DISPLAY_XGA, - LCD_DISPLAY_SXGA, - LCD_DISPLAY_SXGA2, - LCD_DISPLAY_UXGA, - LCD_DISPLAY_HD, - LCD_DISPLAY_FHD, - LCD_DISPLAY_MAX -} -lcd_resolution = LCD_DISPLAY_QVGA; - -static int lcd_refresh = 0; -static int lcd_intensity = 0; - -#ifdef OMV_SPI_LCD_SPI_BUS -static omv_spi_t spi_bus = {}; - -static bool spi_tx_thread_running = false; -static uint32_t spi_tx_baudrate = 0; - -static void spi_transmit(uint8_t *txdata, uint16_t size, bool end) { - omv_spi_transfer_t spi_xfer = { - .txbuf = txdata, - .size = size, - .timeout = OMV_SPI_MAX_TIMEOUT, - .flags = OMV_SPI_XFER_BLOCKING - }; - - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 0); - omv_spi_transfer_start(&spi_bus, &spi_xfer); - - if (end) { - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 1); - } -} - -static void spi_transmit_16(uint8_t *txdata, uint16_t size) { - omv_spi_transfer_t spi_xfer = { - .txbuf = txdata, - .size = (!lcd_byte_reverse) ? size : (size * 2), - .timeout = OMV_SPI_MAX_TIMEOUT, - .flags = OMV_SPI_XFER_BLOCKING, - }; - - omv_spi_transfer_start(&spi_bus, &spi_xfer); -} - -static void spi_config_deinit() { - if (lcd_triple_buffer) { - omv_spi_transfer_abort(&spi_bus); - spi_tx_thread_running = false; - fb_alloc_free_till_mark_past_mark_permanent(); - } - - spi_tx_baudrate = 0; - - omv_spi_deinit(&spi_bus); - - omv_gpio_deinit(OMV_SPI_LCD_RS_PIN); - omv_gpio_deinit(OMV_SPI_LCD_RST_PIN); -} - -static void spi_config_init(int w, int h, int refresh_rate, bool triple_buffer, bool bgr, bool byte_reverse) { - omv_spi_config_t spi_config; - omv_spi_default_config(&spi_config, OMV_SPI_LCD_SPI_BUS); - - spi_tx_baudrate = w * h * refresh_rate * 16; - spi_config.baudrate = spi_tx_baudrate; - spi_config.bus_mode = OMV_SPI_BUS_TX; - spi_config.nss_enable = false; - omv_spi_init(&spi_bus, &spi_config); - - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 1); - - omv_gpio_config(OMV_SPI_LCD_RST_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); - omv_gpio_write(OMV_SPI_LCD_RST_PIN, 1); - - omv_gpio_config(OMV_SPI_LCD_RS_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 1); - - omv_gpio_write(OMV_SPI_LCD_RST_PIN, 0); - mp_hal_delay_ms(100); - omv_gpio_write(OMV_SPI_LCD_RST_PIN, 1); - mp_hal_delay_ms(100); - - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 0); - spi_transmit((uint8_t []) {0x11}, 1, true); // sleep out - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 1); - mp_hal_delay_ms(120); - - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 0); - spi_transmit((uint8_t []) {0x36}, 1, true); // memory data access control - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 1); - spi_transmit((uint8_t []) {bgr ? 0xC8 : 0xC0}, 1, true); // argument - - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 0); - spi_transmit((uint8_t []) {0x3A}, 1, true); // interface pixel format - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 1); - spi_transmit((uint8_t []) {0x05}, 1, true); // argument - - if (triple_buffer) { - fb_alloc_mark(); - - framebuffer_tail = 0; - framebuffer_head = 0; - - for (int i = 0; i < FRAMEBUFFER_COUNT; i++) { - framebuffers[i] = (uint16_t *) fb_alloc0(w * h * sizeof(uint16_t), FB_ALLOC_CACHE_ALIGN); - } - - fb_alloc_mark_permanent(); - } -} - -static const uint8_t display_off[] = {0x28}; -static const uint8_t display_on[] = {0x29}; -static const uint8_t memory_write[] = {0x2C}; - -static void spi_lcd_switch_mode(int bits, bool dma) { - omv_spi_deinit(&spi_bus); - - omv_spi_config_t spi_config; - omv_spi_default_config(&spi_config, OMV_SPI_LCD_SPI_BUS); - - spi_config.baudrate = spi_tx_baudrate; - spi_config.datasize = bits; - spi_config.bus_mode = OMV_SPI_BUS_TX; - spi_config.nss_enable = false; - spi_config.dma_flags = dma ? OMV_SPI_DMA_NORMAL : 0; - omv_spi_init(&spi_bus, &spi_config); -} - -static void spi_lcd_callback(omv_spi_t *spi, void *userdata, void *buf) { - if (lcd_type == LCD_SHIELD) { - static uint8_t *spi_tx_cb_state_memory_write_addr = NULL; - static size_t spi_tx_cb_state_memory_write_count = 0; - - // If userdata is not null then it means that we are being kicked off. - if (userdata != NULL) { - spi_tx_cb_state_memory_write_count = 0; - } - - if (!spi_tx_cb_state_memory_write_count) { - spi_tx_cb_state_memory_write_addr = (uint8_t *) framebuffers[framebuffer_tail]; - spi_tx_cb_state_memory_write_count = lcd_width * lcd_height; - - if (lcd_byte_reverse) { - spi_tx_cb_state_memory_write_count *= 2; - } - - framebuffer_head = framebuffer_tail; - } - - size_t spi_tx_cb_state_memory_write_limit = (!lcd_byte_reverse) - ? OMV_SPI_MAX_16BIT_XFER : OMV_SPI_MAX_8BIT_XFER; - uint8_t *addr = spi_tx_cb_state_memory_write_addr; - size_t count = IM_MIN(spi_tx_cb_state_memory_write_count, spi_tx_cb_state_memory_write_limit); - spi_tx_cb_state_memory_write_addr += (!lcd_byte_reverse) ? (count * 2) : count; - spi_tx_cb_state_memory_write_count -= count; - // When starting the interrupt chain the first transfer is not executed - // in interrupt context. So, disable interrupts for the first transfer so - // that it completes first and unlocks the SPI bus before allowing the interrupt - // it causes to trigger starting the interrupt chain. - omv_spi_transfer_t spi_xfer = { - .txbuf = addr, - .size = count, - .flags = OMV_SPI_XFER_DMA, - .callback = spi_lcd_callback, - }; - if (userdata != NULL) { - uint32_t irq_state = disable_irq(); - omv_spi_transfer_start(&spi_bus, &spi_xfer); - enable_irq(irq_state); - } else { - omv_spi_transfer_start(&spi_bus, &spi_xfer); - } - } -} - -static void spi_lcd_kick() { - if (!spi_tx_thread_running) { - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 0); - spi_transmit((uint8_t *) memory_write, sizeof(memory_write), true); - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 1); - - spi_lcd_switch_mode((!lcd_byte_reverse) ? 16 : 8, true); - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 0); - - // Limit the transfer size to single lines as you cannot send more - // than 64KB per SPI transaction generally. - for (int i = 0; i < lcd_height; i++) { - spi_transmit_16((uint8_t *) (framebuffers[framebuffer_tail] + (lcd_width * i)), lcd_width); - } - - spi_lcd_switch_mode(8, false); - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 1); - - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 0); - spi_transmit((uint8_t *) display_on, sizeof(display_on), true); - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 1); - - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 0); - spi_transmit((uint8_t *) memory_write, sizeof(memory_write), true); - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 1); - - spi_lcd_switch_mode((!lcd_byte_reverse) ? 16 : 8, true); - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 0); - - // Kickoff interrupt driven image update. - spi_tx_thread_running = true; - spi_lcd_callback(&spi_bus, &spi_bus, NULL); - } -} - -static void spi_lcd_draw_image_cb(int x_start, int x_end, int y_row, imlib_draw_row_data_t *data) { - spi_transmit_16(data->dst_row_override, lcd_width); -} - -static void spi_lcd_display(image_t *src_img, int dst_x_start, int dst_y_start, - float x_scale, float y_scale, rectangle_t *roi, int rgb_channel, int alpha, - const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint) { - image_t dst_img; - dst_img.w = lcd_width; - dst_img.h = lcd_height; - dst_img.pixfmt = PIXFORMAT_RGB565; - - int x0, x1, y0, y1; - bool black = !imlib_draw_image_rectangle(&dst_img, src_img, dst_x_start, dst_y_start, - x_scale, y_scale, roi, alpha, alpha_palette, hint, &x0, &x1, &y0, &y1); - - if (!lcd_triple_buffer) { - dst_img.data = fb_alloc0(lcd_width * sizeof(uint16_t), FB_ALLOC_NO_HINT); - - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 0); - spi_transmit((uint8_t *) memory_write, sizeof(memory_write), true); - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 1); - - spi_lcd_switch_mode((!lcd_byte_reverse) ? 16 : 8, true); - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 0); - - if (black) { - // zero the whole image - for (int i = 0; i < lcd_height; i++) { - spi_transmit_16(dst_img.data, lcd_width); - } - } else { - // Zero the top rows - for (int i = 0; i < y0; i++) { - spi_transmit_16(dst_img.data, lcd_width); - } - - // Transmits left/right parts already zeroed... - imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, - x_scale, y_scale, roi, rgb_channel, alpha, color_palette, - alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND, spi_lcd_draw_image_cb, dst_img.data); - - // Zero the bottom rows - if (y1 < lcd_height) { - memset(dst_img.data, 0, lcd_width * sizeof(uint16_t)); - } - - for (int i = y1; i < lcd_height; i++) { - spi_transmit_16(dst_img.data, lcd_width); - } - } - - spi_lcd_switch_mode(8, false); - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 1); - - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 0); - spi_transmit((uint8_t *) display_on, sizeof(display_on), true); - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 1); - - fb_free(); - } else { - // For triple buffering we are never drawing where tail or head (which may instantly update to - // to be equal to tail) is. - int new_framebuffer_tail = (framebuffer_tail + 1) % FRAMEBUFFER_COUNT; - if (new_framebuffer_tail == framebuffer_head) { - new_framebuffer_tail = (new_framebuffer_tail + 1) % FRAMEBUFFER_COUNT; - } - dst_img.data = (uint8_t *) framebuffers[new_framebuffer_tail]; - - if (black) { - // zero the whole image - memset(dst_img.data, 0, lcd_width * lcd_height * sizeof(uint16_t)); - } else { - // Zero the top rows - if (y0) { - memset(dst_img.data, 0, lcd_width * y0 * sizeof(uint16_t)); - } - - if (x0) { - for (int i = y0; i < y1; i++) { - // Zero left - memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i), 0, x0 * sizeof(uint16_t)); - } - } - - imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, - x_scale, y_scale, roi, rgb_channel, alpha, color_palette, - alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL); - - if (lcd_width - x1) { - for (int i = y0; i < y1; i++) { - // Zero right - memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i) + x1, 0, (lcd_width - x1) * sizeof(uint16_t)); - } - } - - // Zero the bottom rows - if (lcd_height - y1) { - memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, y1), - 0, lcd_width * (lcd_height - y1) * sizeof(uint16_t)); - } - } - - #ifdef __DCACHE_PRESENT - // Flush data for DMA - SCB_CleanDCache_by_Addr((uint32_t *) dst_img.data, image_size(&dst_img)); - #endif - - // Update tail which means a new image is ready. - framebuffer_tail = new_framebuffer_tail; - - // Kick off an update of the display. - spi_lcd_kick(); - } -} - -static void spi_lcd_clear() { - if (spi_tx_thread_running) { - omv_spi_transfer_abort(&spi_bus); - spi_tx_thread_running = false; - spi_lcd_switch_mode(8, false); - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 1); - } - - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 0); - spi_transmit((uint8_t *) display_off, sizeof(display_off), true); - omv_gpio_write(OMV_SPI_LCD_RS_PIN, 1); -} - -#ifdef OMV_SPI_LCD_BL_DAC -static DAC_HandleTypeDef lcd_dac_handle = {}; -#endif - -#ifdef OMV_SPI_LCD_BL_PIN -static void spi_lcd_set_backlight(int intensity) { - #ifdef OMV_SPI_LCD_BL_DAC - if ((lcd_intensity < 255) && (255 <= intensity)) { - #else - if ((lcd_intensity < 1) && (1 <= intensity)) { - #endif - omv_gpio_write(OMV_SPI_LCD_BL_PIN, 1); - omv_gpio_deinit(OMV_SPI_LCD_BL_PIN); - } else if ((0 < lcd_intensity) && (intensity <= 0)) { - omv_gpio_config(OMV_SPI_LCD_BL_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); - omv_gpio_write(OMV_SPI_LCD_BL_PIN, 0); - } - - #ifdef OMV_SPI_LCD_BL_DAC - if (((lcd_intensity <= 0) || (255 <= lcd_intensity)) && (0 < intensity) && (intensity < 255)) { - omv_gpio_config(OMV_SPI_LCD_BL_PIN, OMV_GPIO_MODE_ANALOG, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); - - DAC_ChannelConfTypeDef lcd_dac_channel_handle; - lcd_dac_handle.Instance = OMV_SPI_LCD_BL_DAC; - lcd_dac_channel_handle.DAC_Trigger = DAC_TRIGGER_NONE; - lcd_dac_channel_handle.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; - #if defined(MCU_SERIES_H7) - lcd_dac_channel_handle.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE; - lcd_dac_channel_handle.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_DISABLE; - lcd_dac_channel_handle.DAC_UserTrimming = DAC_TRIMMING_FACTORY; - #endif - - HAL_DAC_Init(&lcd_dac_handle); - HAL_DAC_ConfigChannel(&lcd_dac_handle, &lcd_dac_channel_handle, OMV_SPI_LCD_BL_DAC_CHANNEL); - HAL_DAC_Start(&lcd_dac_handle, OMV_SPI_LCD_BL_DAC_CHANNEL); - HAL_DAC_SetValue(&lcd_dac_handle, OMV_SPI_LCD_BL_DAC_CHANNEL, DAC_ALIGN_8B_R, intensity); - } else if ((0 < lcd_intensity) && (lcd_intensity < 255) && ((intensity <= 0) || (255 <= intensity))) { - HAL_DAC_Stop(&lcd_dac_handle, OMV_SPI_LCD_BL_DAC_CHANNEL); - HAL_DAC_DeInit(&lcd_dac_handle); - } else if ((0 < lcd_intensity) && (lcd_intensity < 255) && (0 < intensity) && (intensity < 255)) { - HAL_DAC_SetValue(&lcd_dac_handle, OMV_SPI_LCD_BL_DAC_CHANNEL, DAC_ALIGN_8B_R, intensity); - } - #endif - - lcd_intensity = intensity; -} -#endif // OMV_SPI_LCD_BL_PIN -#endif // OMV_SPI_LCD_SPI_BUS - -#ifdef OMV_LCD_CONTROLLER -static const uint32_t resolution_clock[] = { - // CVT-RB ver 2 @ 60 FPS - 6144, // QVGA - 6426, // TQVGA - 9633, // FHVGA - 4799, // FHVGA2 - 21363, // VGA - 11868, // THVGA - 26110, // FWVGA - 17670, // FWVGA2 - 27624, // TFWVGA - 16615, // TFWVGA2 - 32597, // SVGA - 40895, // WSVGA - 52277, // XGA - 85920, // SXGA - 33830, // SXGA2 - 124364, // UXGA - 60405, // HD - 133187 // FHD -}; - -static const uint16_t resolution_w_h[][2] = { - {320, 240}, // QVGA - {240, 320}, // TQVGA - {480, 272}, // FHVGA - {480, 128}, // FHVGA2 - {640, 480}, // VGA - {320, 480}, // THVGA - {800, 480}, // FWVGA - {800, 320}, // FWVGA2 - {480, 800}, // TFWVGA - {480, 480}, // TFWVGA2 - {800, 600}, // SVGA - {1024, 600}, // WSVGA - {1024, 768}, // XGA - {1280, 1024}, // SXGA - {1280, 400}, // SXGA2 - {1600, 1200}, // UXGA - {1280, 720}, // HD - {1920, 1080} // FHD -}; - -static const LTDC_InitTypeDef resolution_cfg[] = { - // CVT-RB ver 2 - { // QVGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 320 - 1, - .AccumulatedActiveH = 8 + 6 + 240 - 1, - .TotalWidth = 32 + 40 + 320 + 8 - 1, - .TotalHeigh = 8 + 6 + 240 + 1 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // TQVGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 240 - 1, - .AccumulatedActiveH = 8 + 6 + 320 - 1, - .TotalWidth = 32 + 40 + 240 + 8 - 1, - .TotalHeigh = 8 + 6 + 320 + 1 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // FHVGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 480 - 1, - .AccumulatedActiveH = 8 + 6 + 272 - 1, - .TotalWidth = 32 + 40 + 480 + 8 - 1, - .TotalHeigh = 8 + 6 + 272 + 1 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // FHVGA2 - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 480 - 1, - .AccumulatedActiveH = 8 + 6 + 128 - 1, - .TotalWidth = 32 + 40 + 480 + 8 - 1, - .TotalHeigh = 8 + 6 + 128 + 1 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // VGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 640 - 1, - .AccumulatedActiveH = 8 + 6 + 480 - 1, - .TotalWidth = 32 + 40 + 640 + 8 - 1, - .TotalHeigh = 8 + 6 + 480 + 1 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // THVGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 320 - 1, - .AccumulatedActiveH = 8 + 6 + 480 - 1, - .TotalWidth = 32 + 40 + 320 + 8 - 1, - .TotalHeigh = 8 + 6 + 480 + 1 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // FWVGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 800 - 1, - .AccumulatedActiveH = 8 + 6 + 480 - 1, - .TotalWidth = 32 + 40 + 800 + 8 - 1, - .TotalHeigh = 8 + 6 + 480 + 1 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // FWVGA2 - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 800 - 1, - .AccumulatedActiveH = 8 + 6 + 320 - 1, - .TotalWidth = 32 + 40 + 800 + 8 - 1, - .TotalHeigh = 8 + 6 + 320 + 1 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // TFWVGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 480 - 1, - .AccumulatedActiveH = 8 + 6 + 800 - 1, - .TotalWidth = 32 + 40 + 480 + 8 - 1, - .TotalHeigh = 8 + 6 + 800 + 9 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // TFWVGA2 - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 480 - 1, - .AccumulatedActiveH = 8 + 6 + 480 - 1, - .TotalWidth = 32 + 40 + 480 + 8 - 1, - .TotalHeigh = 8 + 6 + 480 + 1 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // SVGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 800 - 1, - .AccumulatedActiveH = 8 + 6 + 600 - 1, - .TotalWidth = 32 + 40 + 800 + 8 - 1, - .TotalHeigh = 8 + 6 + 600 + 4 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // WSVGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 1024 - 1, - .AccumulatedActiveH = 8 + 6 + 600 - 1, - .TotalWidth = 32 + 40 + 1024 + 8 - 1, - .TotalHeigh = 8 + 6 + 600 + 4 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // XGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 1024 - 1, - .AccumulatedActiveH = 8 + 6 + 768 - 1, - .TotalWidth = 32 + 40 + 1024 + 8 - 1, - .TotalHeigh = 8 + 6 + 768 + 8 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // SXGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 1280 - 1, - .AccumulatedActiveH = 8 + 6 + 1024 - 1, - .TotalWidth = 32 + 40 + 1280 + 8 - 1, - .TotalHeigh = 8 + 6 + 1024 + 16 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // SXGA2 - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 1280 - 1, - .AccumulatedActiveH = 8 + 6 + 400 - 1, - .TotalWidth = 32 + 40 + 1280 + 8 - 1, - .TotalHeigh = 8 + 6 + 400 + 1 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // UXGA - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 1600 - 1, - .AccumulatedActiveH = 8 + 6 + 1200 - 1, - .TotalWidth = 32 + 40 + 1600 + 8 - 1, - .TotalHeigh = 8 + 6 + 1200 + 21 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // HD - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 1280 - 1, - .AccumulatedActiveH = 8 + 6 + 720 - 1, - .TotalWidth = 32 + 40 + 1280 + 8 - 1, - .TotalHeigh = 8 + 6 + 720 + 7 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - }, - { // FHD - .HSPolarity = LTDC_HSPOLARITY_AH, - .VSPolarity = LTDC_VSPOLARITY_AL, - .DEPolarity = LTDC_DEPOLARITY_AL, - .PCPolarity = LTDC_PCPOLARITY_IPC, - .HorizontalSync = 32 - 1, - .VerticalSync = 8 - 1, - .AccumulatedHBP = 32 + 40 - 1, - .AccumulatedVBP = 8 + 6 - 1, - .AccumulatedActiveW = 32 + 40 + 1920 - 1, - .AccumulatedActiveH = 8 + 6 + 1080 - 1, - .TotalWidth = 32 + 40 + 1920 + 8 - 1, - .TotalHeigh = 8 + 6 + 1080 + 17 - 1, - .Backcolor = {.Blue = 0, .Green = 0, .Red = 0} - } -}; - -static LTDC_HandleTypeDef ltdc_handle = {}; -static LTDC_LayerCfgTypeDef ltdc_framebuffer_layers[FRAMEBUFFER_COUNT] = {}; - -static void ltdc_pll_config_deinit() { - __HAL_RCC_PLL3_DISABLE(); - - uint32_t tickstart = mp_hal_ticks_ms(); - - while (__HAL_RCC_GET_FLAG(RCC_FLAG_PLL3RDY)) { - if ((mp_hal_ticks_ms() - tickstart) > PLL_TIMEOUT_VALUE) { - break; - } - } -} - -static void ltdc_pll_config_init(int frame_size, int refresh_rate) { - uint32_t pixel_clock = (resolution_clock[frame_size] * refresh_rate) / 60; - - for (uint32_t divm = 1; divm <= 63; divm++) { - for (uint32_t divr = 1; divr <= 128; divr++) { - - uint32_t ref_clk = (HSE_VALUE / 1000) / divm; - - uint32_t vci = 0; - if (1000 <= ref_clk && ref_clk <= 2000) { - vci = RCC_PLL3VCIRANGE_0; - } else if (2000 <= ref_clk && ref_clk <= 4000) { - vci = RCC_PLL3VCIRANGE_1; - } else if (4000 <= ref_clk && ref_clk <= 8000) { - vci = RCC_PLL3VCIRANGE_2; - } else if (8000 <= ref_clk && ref_clk <= 16000) { - vci = RCC_PLL3VCIRANGE_3; - } else { - continue; - } - - uint32_t pll_clk = pixel_clock * divr; - - uint32_t vco = 0; - if (150000 <= pll_clk && pll_clk <= 420000) { - vco = RCC_PLL3VCOMEDIUM; - } else if (192000 <= pll_clk && pll_clk <= 836000) { - vco = RCC_PLL3VCOWIDE; - } else { - continue; - } - - uint32_t divn = pll_clk / ref_clk; - if (divn < 4 || 512 < divn) { - continue; - } - - uint32_t frac = ((pll_clk % ref_clk) * 8192) / ref_clk; - - RCC_PeriphCLKInitTypeDef init; - init.PeriphClockSelection = RCC_PERIPHCLK_LTDC; - init.PLL3.PLL3M = divm; - init.PLL3.PLL3N = divn; - init.PLL3.PLL3P = 128; - init.PLL3.PLL3Q = 128; - init.PLL3.PLL3R = divr; - init.PLL3.PLL3RGE = vci; - init.PLL3.PLL3VCOSEL = vco; - init.PLL3.PLL3FRACN = frac; - - if (HAL_RCCEx_PeriphCLKConfig(&init) == HAL_OK) { - return; - } - } - } - - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unable to initialize LTDC PLL!")); -} - -static void ltdc_config_deinit() { - HAL_LTDC_DeInit(<dc_handle); - ltdc_pll_config_deinit(); - fb_alloc_free_till_mark_past_mark_permanent(); -} - -static void ltdc_config_init(int frame_size, int refresh_rate) { - int w = resolution_w_h[frame_size][0]; - int h = resolution_w_h[frame_size][1]; - - fb_alloc_mark(); - - framebuffer_tail = 0; - framebuffer_head = 0; - - for (int i = 0; i < FRAMEBUFFER_COUNT; i++) { - framebuffers[i] = (uint16_t *) fb_alloc0(w * h * sizeof(uint16_t), FB_ALLOC_CACHE_ALIGN); - ltdc_framebuffer_layers[i].WindowX0 = 0; - ltdc_framebuffer_layers[i].WindowX1 = w; - ltdc_framebuffer_layers[i].WindowY0 = 0; - ltdc_framebuffer_layers[i].WindowY1 = h; - ltdc_framebuffer_layers[i].PixelFormat = LTDC_PIXEL_FORMAT_RGB565; - ltdc_framebuffer_layers[i].Alpha = 0; - ltdc_framebuffer_layers[i].Alpha0 = 0; - ltdc_framebuffer_layers[i].BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA; - ltdc_framebuffer_layers[i].BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA; - ltdc_framebuffer_layers[i].FBStartAdress = (uint32_t) framebuffers[i]; - ltdc_framebuffer_layers[i].ImageWidth = w; - ltdc_framebuffer_layers[i].ImageHeight = h; - ltdc_framebuffer_layers[i].Backcolor.Blue = 0; - ltdc_framebuffer_layers[i].Backcolor.Green = 0; - ltdc_framebuffer_layers[i].Backcolor.Red = 0; - } - - ltdc_pll_config_init(frame_size, refresh_rate); - - ltdc_handle.Instance = LTDC; - memcpy(<dc_handle.Init, &resolution_cfg[frame_size], sizeof(LTDC_InitTypeDef)); - - HAL_LTDC_Init(<dc_handle); - - NVIC_SetPriority(LTDC_IRQn, IRQ_PRI_LTDC); - HAL_NVIC_EnableIRQ(LTDC_IRQn); - - fb_alloc_mark_permanent(); - - // Start interrupt chain. - HAL_LTDC_ProgramLineEvent(<dc_handle, 13); // AccumulatedVBP -} - -void LTDC_IRQHandler() { - IRQ_ENTER(LTDC_IRQn); - HAL_LTDC_IRQHandler(<dc_handle); - IRQ_EXIT(LTDC_IRQn); -} - -void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef *hltdc) { - HAL_LTDC_ConfigLayer_NoReload(<dc_handle, <dc_framebuffer_layers[framebuffer_tail], LTDC_LAYER_1); - HAL_LTDC_Reload(<dc_handle, LTDC_RELOAD_VERTICAL_BLANKING); - - #if defined(OMV_LCD_DISP_PIN) - if (((lcd_type == LCD_DISPLAY) || (lcd_type == LCD_DISPLAY_WITH_HDMI)) - && (framebuffer_tail != framebuffer_head)) { - // Turn display on if there is a new command. - omv_gpio_write(OMV_LCD_DISP_PIN, 1); - } - #endif - framebuffer_head = framebuffer_tail; - - // Continue chain... - HAL_LTDC_ProgramLineEvent(<dc_handle, 13); // AccumulatedVBP -} - -static void ltdc_display(image_t *src_img, int dst_x_start, int dst_y_start, - float x_scale, float y_scale, rectangle_t *roi, int rgb_channel, int alpha, - const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint) { - image_t dst_img; - dst_img.w = lcd_width; - dst_img.h = lcd_height; - dst_img.pixfmt = PIXFORMAT_RGB565; - - int x0, x1, y0, y1; - bool black = !imlib_draw_image_rectangle(&dst_img, - src_img, - dst_x_start, - dst_y_start, - x_scale, - y_scale, - roi, - alpha, - alpha_palette, - hint, - &x0, - &x1, - &y0, - &y1); - - // For triple buffering we are never drawing where tail or head (which may instantly update to - // to be equal to tail) is. - int new_framebuffer_tail = (framebuffer_tail + 1) % FRAMEBUFFER_COUNT; - if (new_framebuffer_tail == framebuffer_head) { - new_framebuffer_tail = (new_framebuffer_tail + 1) % FRAMEBUFFER_COUNT; - } - dst_img.data = (uint8_t *) framebuffers[new_framebuffer_tail]; - - // Set default values for the layer to display the whole framebuffer. - ltdc_framebuffer_layers[new_framebuffer_tail].WindowX0 = black ? 0 : x0; - ltdc_framebuffer_layers[new_framebuffer_tail].WindowX1 = black ? lcd_width : x1; - ltdc_framebuffer_layers[new_framebuffer_tail].WindowY0 = black ? 0 : y0; - ltdc_framebuffer_layers[new_framebuffer_tail].WindowY1 = black ? lcd_height : y1; - ltdc_framebuffer_layers[new_framebuffer_tail].Alpha = black ? 0 : fast_roundf((alpha * 255) / 256.f); - ltdc_framebuffer_layers[new_framebuffer_tail].FBStartAdress = - black ? ((uint32_t) dst_img.data) : ((uint32_t) (IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, y0) + x0)); - ltdc_framebuffer_layers[new_framebuffer_tail].ImageWidth = black ? lcd_width : dst_img.w; - ltdc_framebuffer_layers[new_framebuffer_tail].ImageHeight = black ? lcd_height : (y1 - y0); - - // Set alpha to 256 here as we will use the layer alpha to blend the image into the background color of black for free. - if (!black) { - imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, - x_scale, y_scale, roi, rgb_channel, 256, color_palette, - alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL); - } - - #ifdef __DCACHE_PRESENT - // Flush data for DMA - if (!black) { - SCB_CleanDCache_by_Addr((uint32_t *) dst_img.data, image_size(&dst_img)); - } - #endif - - // Update tail which means a new image is ready. - framebuffer_tail = new_framebuffer_tail; -} - -static void ltdc_clear() { - // For triple buffering we are never drawing where tail or head (which may instantly update to - // to be equal to tail) is. - int new_framebuffer_tail = (framebuffer_tail + 1) % FRAMEBUFFER_COUNT; - if (new_framebuffer_tail == framebuffer_head) { - new_framebuffer_tail = (new_framebuffer_tail + 1) % FRAMEBUFFER_COUNT; - } - - // Set default values for the layer to display the whole framebuffer. - ltdc_framebuffer_layers[new_framebuffer_tail].WindowX0 = 0; - ltdc_framebuffer_layers[new_framebuffer_tail].WindowX1 = lcd_width; - ltdc_framebuffer_layers[new_framebuffer_tail].WindowY0 = 0; - ltdc_framebuffer_layers[new_framebuffer_tail].WindowY1 = lcd_height; - ltdc_framebuffer_layers[new_framebuffer_tail].Alpha = 0; - ltdc_framebuffer_layers[new_framebuffer_tail].FBStartAdress = (uint32_t) framebuffers[new_framebuffer_tail]; - ltdc_framebuffer_layers[new_framebuffer_tail].ImageWidth = lcd_width; - ltdc_framebuffer_layers[new_framebuffer_tail].ImageHeight = lcd_height; - - // Update tail which means a new image is ready. - framebuffer_tail = new_framebuffer_tail; -} - -#ifdef OMV_LCD_BL_TIM -static TIM_HandleTypeDef lcd_tim_handle = {}; -#endif - -#ifdef OMV_LCD_BL_PIN -static void ltdc_set_backlight(int intensity) { - #ifdef OMV_LCD_BL_TIM - if ((lcd_intensity < 255) && (255 <= intensity)) { - #else - if ((lcd_intensity < 1) && (1 <= intensity)) { - #endif - omv_gpio_config(OMV_LCD_BL_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); - omv_gpio_write(OMV_LCD_BL_PIN, 1); - } else if ((0 < lcd_intensity) && (intensity <= 0)) { - omv_gpio_write(OMV_LCD_BL_PIN, 0); - omv_gpio_deinit(OMV_LCD_BL_PIN); - } - - #ifdef OMV_LCD_BL_TIM - int tclk = OMV_LCD_BL_TIM_PCLK_FREQ() * 2; - int period = (tclk / OMV_LCD_BL_FREQ) - 1; - - if (((lcd_intensity <= 0) || (255 <= lcd_intensity)) && (0 < intensity) && (intensity < 255)) { - omv_gpio_config(OMV_LCD_BL_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); - - lcd_tim_handle.Instance = OMV_LCD_BL_TIM; - lcd_tim_handle.Init.Prescaler = 0; - lcd_tim_handle.Init.CounterMode = TIM_COUNTERMODE_UP; - lcd_tim_handle.Init.Period = period; - lcd_tim_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; - lcd_tim_handle.Init.RepetitionCounter = 0; - lcd_tim_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; - - TIM_OC_InitTypeDef lcd_tim_oc_handle; - lcd_tim_oc_handle.Pulse = (period * intensity) / 255; - lcd_tim_oc_handle.OCMode = TIM_OCMODE_PWM1; - lcd_tim_oc_handle.OCPolarity = TIM_OCPOLARITY_HIGH; - lcd_tim_oc_handle.OCNPolarity = TIM_OCNPOLARITY_HIGH; - lcd_tim_oc_handle.OCFastMode = TIM_OCFAST_DISABLE; - lcd_tim_oc_handle.OCIdleState = TIM_OCIDLESTATE_RESET; - lcd_tim_oc_handle.OCNIdleState = TIM_OCNIDLESTATE_RESET; - - HAL_TIM_PWM_Init(&lcd_tim_handle); - HAL_TIM_PWM_ConfigChannel(&lcd_tim_handle, &lcd_tim_oc_handle, OMV_LCD_BL_TIM_CHANNEL); - HAL_TIM_PWM_Start(&lcd_tim_handle, OMV_LCD_BL_TIM_CHANNEL); - } else if ((0 < lcd_intensity) && (lcd_intensity < 255) && ((intensity <= 0) || (255 <= intensity))) { - HAL_TIM_PWM_Stop(&lcd_tim_handle, OMV_LCD_BL_TIM_CHANNEL); - HAL_TIM_PWM_DeInit(&lcd_tim_handle); - } else if ((0 < lcd_intensity) && (lcd_intensity < 255) && (0 < intensity) && (intensity < 255)) { - __HAL_TIM_SET_COMPARE(&lcd_tim_handle, OMV_LCD_BL_TIM_CHANNEL, (period * intensity) / 255); - } - #endif - - lcd_intensity = intensity; -} -#endif // OMV_LCD_BL_PIN -#endif // OMV_LCD_CONTROLLER - -#ifdef OMV_DVI_PRESENT -#define TFP410_I2C_ADDR 0x3F -mp_obj_base_t *ltdc_dvi_bus = NULL; -#ifdef OMV_DDC_PRESENT -#define EEPROM_I2C_ADDR 0x50 -mp_obj_base_t *ltdc_ddc_bus = NULL; -#endif // OMV_DDC_PRESENT -mp_obj_t ltdc_dvi_user_cb = NULL; - -static mp_obj_t ltdc_dvi_get_display_connected() { - mp_obj_base_t *bus = ltdc_dvi_bus ? ltdc_dvi_bus : ((mp_obj_base_t *) MP_OBJ_TYPE_GET_SLOT( - &mp_machine_soft_i2c_type, make_new) (&mp_machine_soft_i2c_type, 2, - 0, (const mp_obj_t []) { - (mp_obj_t) OMV_DVI_SCL_PIN, (mp_obj_t) OMV_DVI_SDA_PIN - })); - - if (mp_machine_soft_i2c_transfer(bus, TFP410_I2C_ADDR, 1, &((mp_machine_i2c_buf_t) { - .len = 1, .buf = (uint8_t []) {0x09} // addr - }), 0) == 1) { - uint8_t reg; - - if ((mp_machine_soft_i2c_transfer(bus, TFP410_I2C_ADDR, 1, &((mp_machine_i2c_buf_t) { - .len = 1, .buf = ® - }), MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP) == 0) - && (mp_machine_soft_i2c_transfer(bus, TFP410_I2C_ADDR, 1, &((mp_machine_i2c_buf_t) { - .len = 2, .buf = (uint8_t []) {0x09, 0x19} // clear interrupt flag - }), MP_MACHINE_I2C_FLAG_STOP) == 2)) { - return mp_obj_new_bool(reg & 2); - } - } else { - // generate stop on error... - mp_machine_soft_i2c_transfer(bus, TFP410_I2C_ADDR, 1, &((mp_machine_i2c_buf_t) { - .len = 0, .buf = NULL - }), MP_MACHINE_I2C_FLAG_STOP); - } - - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to get display connected!")); -} - -#ifdef OMV_DDC_PRESENT -static bool ltdc_dvi_checksum(uint8_t *data, int long_count) { - uint32_t *data32 = (uint32_t *) data; - uint32_t sum = 0; - - for (int i = 0; i < long_count; i++) { - sum = __USADA8(data32[i], 0, sum); - } - - return !(sum & 0xFF); -} - -static mp_obj_t ltdc_dvi_get_display_id_data() { - mp_obj_base_t *bus = ltdc_ddc_bus ? ltdc_ddc_bus : - ((mp_obj_base_t *) MP_OBJ_TYPE_GET_SLOT( - &mp_machine_soft_i2c_type, make_new) (&mp_machine_soft_i2c_type, 2, 1, (const mp_obj_t []) { - (mp_obj_t) OMV_DDC_SCL_PIN, (mp_obj_t) OMV_DDC_SDA_PIN, - MP_OBJ_NEW_QSTR(MP_QSTR_freq), - MP_OBJ_NEW_SMALL_INT(100000) - })); - - if (mp_machine_soft_i2c_transfer(bus, EEPROM_I2C_ADDR, 1, &((mp_machine_i2c_buf_t) { - .len = 1, .buf = (uint8_t []) {0x00} // addr - }), MP_MACHINE_I2C_FLAG_STOP) == 1) { - fb_alloc_mark(); - uint8_t *data = fb_alloc(128, FB_ALLOC_NO_HINT); - - if (mp_machine_soft_i2c_transfer(bus, EEPROM_I2C_ADDR, 1, &((mp_machine_i2c_buf_t) { - .len = 128, .buf = data - }), MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP) == 0) { - uint32_t *data32 = (uint32_t *) data; - - if ((data32[0] == 0xFFFFFF00) && (data32[1] == 0x00FFFFFF) && ltdc_dvi_checksum(data, 32) - && (mp_machine_soft_i2c_transfer(bus, EEPROM_I2C_ADDR, 1, &((mp_machine_i2c_buf_t) { - .len = 1, .buf = (uint8_t []) {0x80} // addr - }), MP_MACHINE_I2C_FLAG_STOP) == 1)) { - int extensions = data[126]; - int extensions_byte_size = extensions * 128; - int total_data_byte_size = extensions_byte_size + 128; - uint8_t *data2 = fb_alloc(total_data_byte_size, FB_ALLOC_NO_HINT), *data2_ext = data2 + 128; - memcpy(data2, data, 128); - - if ((mp_machine_soft_i2c_transfer(bus, EEPROM_I2C_ADDR, 1, &((mp_machine_i2c_buf_t) { - .len = extensions_byte_size, .buf = data2_ext - }), MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP) == 0) - && ltdc_dvi_checksum(data2_ext, extensions_byte_size / 4)) { - mp_obj_t result = mp_obj_new_bytes(data2, total_data_byte_size); - fb_alloc_free_till_mark(); - return result; - } - } - } - } - - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to get display id data!")); -} -#endif // OMV_DDC_PRESENT - -static void ltdc_dvi_extint_callback(void *data) { - if (ltdc_dvi_user_cb) { - mp_call_function_1(ltdc_dvi_user_cb, ltdc_dvi_get_display_connected()); - } -} - -static void ltdc_dvi_deinit() { - omv_gpio_irq_enable(OMV_DVI_INT_PIN, false); - - ltdc_dvi_user_cb = NULL; - #ifdef OMV_DDC_PRESENT - ltdc_ddc_bus = NULL; - #endif // OMV_DDC_PRESENT - ltdc_dvi_bus = NULL; - - omv_gpio_write(OMV_DVI_RESET_PIN, 0); - mp_hal_delay_ms(1); - - omv_gpio_write(OMV_DVI_RESET_PIN, 1); - mp_hal_delay_ms(1); - - omv_gpio_deinit(OMV_DVI_INT_PIN); - omv_gpio_deinit(OMV_DVI_RESET_PIN); - HAL_GPIO_DeInit(OMV_TOUCH_SDA_PIN->gpio, OMV_TOUCH_SDA_PIN->pin_mask); - HAL_GPIO_DeInit(OMV_TOUCH_SCL_PIN->gpio, OMV_TOUCH_SCL_PIN->pin_mask); -} - -static void ltdc_dvi_init() { - omv_gpio_config(OMV_DVI_RESET_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); - omv_gpio_write(OMV_DVI_RESET_PIN, 0); - mp_hal_delay_ms(1); - omv_gpio_write(OMV_DVI_RESET_PIN, 1); - mp_hal_delay_ms(1); - - ltdc_dvi_bus = (mp_obj_base_t *) MP_OBJ_TYPE_GET_SLOT( - &mp_machine_soft_i2c_type, make_new) (&mp_machine_soft_i2c_type, 2, 0, (const mp_obj_t []) { - (mp_obj_t) OMV_DVI_SCL_PIN, (mp_obj_t) OMV_DVI_SDA_PIN - }); - - #ifdef OMV_DDC_PRESENT - ltdc_ddc_bus = (mp_obj_base_t *) MP_OBJ_TYPE_GET_SLOT( - &mp_machine_soft_i2c_type, make_new) (&mp_machine_soft_i2c_type, 2, 1, (const mp_obj_t []) { - (mp_obj_t) OMV_DDC_SCL_PIN, (mp_obj_t) OMV_DDC_SDA_PIN, - MP_OBJ_NEW_QSTR(MP_QSTR_freq), - MP_OBJ_NEW_SMALL_INT(100000) - }); - #endif // OMV_DDC_PRESENT - - if (mp_machine_soft_i2c_transfer(ltdc_dvi_bus, TFP410_I2C_ADDR, 1, &((mp_machine_i2c_buf_t) { - .len = 4, .buf = (uint8_t []) {0x08, 0xB7, 0x19, 0x80} // addr, CTL_1, CTL_2, CTL_3 - }), MP_MACHINE_I2C_FLAG_STOP) == 4) { - omv_gpio_config(OMV_DVI_INT_PIN, OMV_GPIO_MODE_IT_FALL, OMV_GPIO_PULL_UP, OMV_GPIO_SPEED_LOW, -1); - omv_gpio_irq_register(OMV_DVI_INT_PIN, ltdc_dvi_extint_callback, NULL); - } else { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Display init failed!")); - } -} - -static void ltdc_dvi_register_hotplug_cb(mp_obj_t cb) { - omv_gpio_irq_enable(OMV_DVI_INT_PIN, false); - ltdc_dvi_user_cb = cb; - if (cb != mp_const_none) { - omv_gpio_irq_enable(OMV_DVI_INT_PIN, true); - } -} -#endif // OMV_DVI_PRESENT - -STATIC mp_obj_t py_lcd_deinit() { - switch (lcd_type) { - #ifdef OMV_SPI_LCD_SPI_BUS - case LCD_SHIELD: { - spi_config_deinit(); - #ifdef OMV_SPI_LCD_BL_PIN - // back to default state - spi_lcd_set_backlight(255); - #endif - break; - } - #endif - #ifdef OMV_LCD_CONTROLLER - case LCD_DISPLAY: case LCD_DISPLAY_WITH_HDMI: case LCD_DISPLAY_ONLY_HDMI: { - ltdc_config_deinit(); - #ifdef OMV_LCD_BL_PIN - if ((lcd_type == LCD_DISPLAY) || (lcd_type == LCD_DISPLAY_WITH_HDMI)) { - // back to default state - ltdc_set_backlight(0); - } - #endif // OMV_LCD_BL_PIN - #ifdef OMV_DVI_PRESENT - if ((lcd_type == LCD_DISPLAY_WITH_HDMI) || (lcd_type == LCD_DISPLAY_ONLY_HDMI)) { - ltdc_dvi_deinit(); - #ifdef OMV_CEC_PRESENT - lcd_cec_deinit(); - #endif - } - #endif // OMV_DVI_PRESENT - #ifdef OMV_TOUCH_PRESENT - if ((lcd_type == LCD_DISPLAY) || (lcd_type == LCD_DISPLAY_WITH_HDMI)) { - lcd_touch_deinit(); - } - #endif // OMV_TOUCH_PRESENT - break; - } - #endif - default: { - break; - } - } - - lcd_width = 0; - lcd_height = 0; - lcd_type = LCD_NONE; - lcd_triple_buffer = false; - lcd_bgr = false; - lcd_resolution = 0; - lcd_refresh = 0; - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_deinit_obj, py_lcd_deinit); - -STATIC mp_obj_t py_lcd_init(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - py_lcd_deinit(); - - int type = py_helper_keyword_int(n_args, args, 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_type), LCD_SHIELD); - - switch (type) { - #ifdef OMV_SPI_LCD_SPI_BUS - case LCD_SHIELD: { - int w = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_width), 128); - if ((w <= 0) || (32767 < w)) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Width!")); - } - int h = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_height), 160); - if ((h <= 0) || (32767 < h)) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Height!")); - } - int refresh_rate = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_refresh), 60); - if ((refresh_rate < 30) || (120 < refresh_rate)) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Refresh Rate!")); - } - bool triple_buffer_def = false; - #ifdef OMV_SPI_LCD_DEF_TRIPLE_BUF - triple_buffer_def = OMV_SPI_LCD_DEF_TRIPLE_BUF; - #endif - bool triple_buffer = py_helper_keyword_int(n_args, args, 4, kw_args, - MP_OBJ_NEW_QSTR(MP_QSTR_triple_buffer), triple_buffer_def); - bool bgr = py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bgr), false); - bool byte_reverse = py_helper_keyword_int(n_args, args, 6, kw_args, - MP_OBJ_NEW_QSTR(MP_QSTR_byte_reverse), false); - spi_config_init(w, h, refresh_rate, triple_buffer, bgr, byte_reverse); - #ifdef OMV_SPI_LCD_BL_PIN - spi_lcd_set_backlight(255); // to on state - #endif - lcd_width = w; - lcd_height = h; - lcd_type = LCD_SHIELD; - lcd_triple_buffer = triple_buffer; - lcd_bgr = bgr; - lcd_byte_reverse = byte_reverse; - lcd_resolution = 0; - lcd_refresh = refresh_rate; - break; - } - #endif - #ifdef OMV_LCD_CONTROLLER - case LCD_DISPLAY: case LCD_DISPLAY_WITH_HDMI: case LCD_DISPLAY_ONLY_HDMI: { - int frame_size = py_helper_keyword_int(n_args, - args, - 1, - kw_args, - MP_OBJ_NEW_QSTR(MP_QSTR_framesize), - LCD_DISPLAY_FWVGA); - if ((frame_size < 0) || (LCD_DISPLAY_MAX <= frame_size)) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Frame Size!")); - } - int refresh_rate = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_refresh), 60); - if ((refresh_rate < 30) || (120 < refresh_rate)) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Refresh Rate!")); - } - ltdc_config_init(frame_size, refresh_rate); - #ifdef OMV_LCD_BL_PIN - if ((type == LCD_DISPLAY) || (type == LCD_DISPLAY_WITH_HDMI)) { - ltdc_set_backlight(255); // to on state - } - #endif // OMV_LCD_BL_PIN - #ifdef OMV_DVI_PRESENT - if ((type == LCD_DISPLAY_WITH_HDMI) || (type == LCD_DISPLAY_ONLY_HDMI)) { - ltdc_dvi_init(); - #ifdef OMV_CEC_PRESENT - lcd_cec_init(); - #endif - } - #endif // OMV_DVI_PRESENT - #ifdef OMV_TOUCH_PRESENT - if ((type == LCD_DISPLAY) || (type == LCD_DISPLAY_WITH_HDMI)) { - lcd_touch_init(); - } - #endif // OMV_TOUCH_PRESENT - lcd_width = resolution_w_h[frame_size][0]; - lcd_height = resolution_w_h[frame_size][1]; - lcd_type = LCD_DISPLAY; - lcd_triple_buffer = true; - lcd_bgr = false; - lcd_resolution = frame_size; - lcd_refresh = refresh_rate; - break; - } - #endif - default: { - break; - } - } - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_lcd_init_obj, 0, py_lcd_init); - -STATIC mp_obj_t py_lcd_width() { - if (lcd_type == LCD_NONE) { - return mp_const_none; - } - return mp_obj_new_int(lcd_width); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_width_obj, py_lcd_width); - -STATIC mp_obj_t py_lcd_height() { - if (lcd_type == LCD_NONE) { - return mp_const_none; - } - return mp_obj_new_int(lcd_height); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_height_obj, py_lcd_height); - -STATIC mp_obj_t py_lcd_type() { - if (lcd_type == LCD_NONE) { - return mp_const_none; - } - return mp_obj_new_int(lcd_type); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_type_obj, py_lcd_type); - -STATIC mp_obj_t py_lcd_triple_buffer() { - if (lcd_type == LCD_NONE) { - return mp_const_none; - } - return mp_obj_new_int(lcd_triple_buffer); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_triple_buffer_obj, py_lcd_triple_buffer); - -STATIC mp_obj_t py_lcd_bgr() { - if (lcd_type == LCD_NONE) { - return mp_const_none; - } - return mp_obj_new_int(lcd_bgr); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_bgr_obj, py_lcd_bgr); - -STATIC mp_obj_t py_lcd_byte_reverse() { - if (lcd_type == LCD_NONE) { - return mp_const_none; - } - return mp_obj_new_int(lcd_byte_reverse); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_byte_reverse_obj, py_lcd_byte_reverse); - -STATIC mp_obj_t py_lcd_framesize() { - if ((lcd_type == LCD_NONE) || (lcd_type == LCD_SHIELD)) { - return mp_const_none; - } - return mp_obj_new_int(lcd_resolution); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_framesize_obj, py_lcd_framesize); - -STATIC mp_obj_t py_lcd_refresh() { - if (lcd_type == LCD_NONE) { - return mp_const_none; - } - return mp_obj_new_int(lcd_refresh); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_refresh_obj, py_lcd_refresh); - -STATIC mp_obj_t py_lcd_set_backlight(mp_obj_t intensity_obj) { - int intensity = mp_obj_get_int(intensity_obj); - if ((intensity < 0) || (255 < intensity)) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= intensity <= 255!")); - } - - switch (lcd_type) { - #if defined(OMV_SPI_LCD_SPI_BUS) && defined(OMV_SPI_LCD_BL_PIN) - case LCD_SHIELD: { - spi_lcd_set_backlight(intensity); - break; - } - #endif - #if defined(OMV_LCD_CONTROLLER) && defined(OMV_LCD_BL_PIN) - case LCD_DISPLAY: case LCD_DISPLAY_WITH_HDMI: { - ltdc_set_backlight(intensity); - break; - } - #endif - default: { - break; - } - } - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_lcd_set_backlight_obj, py_lcd_set_backlight); - -STATIC mp_obj_t py_lcd_get_backlight() { - if ((lcd_type == LCD_NONE) || (lcd_type == LCD_DISPLAY_ONLY_HDMI)) { - return mp_const_none; - } - return mp_obj_new_int(lcd_intensity); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_get_backlight_obj, py_lcd_get_backlight); - -#ifdef OMV_DVI_PRESENT -STATIC mp_obj_t py_lcd_get_display_connected() { - return ltdc_dvi_get_display_connected(); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_get_display_connected_obj, py_lcd_get_display_connected); - -STATIC mp_obj_t py_lcd_register_hotplug_cb(mp_obj_t cb) { - ltdc_dvi_register_hotplug_cb(cb); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_lcd_register_hotplug_cb_obj, py_lcd_register_hotplug_cb); -#endif - -#if defined(OMV_DVI_PRESENT) && defined(OMV_DDC_PRESENT) -STATIC mp_obj_t py_lcd_get_display_id_data() { - return ltdc_dvi_get_display_id_data(); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_get_display_id_data_obj, py_lcd_get_display_id_data); -#endif - -#if defined(OMV_DVI_PRESENT) && defined(OMV_CEC_PRESENT) -STATIC mp_obj_t py_lcd_send_frame(mp_obj_t dst_addr, mp_obj_t src_addr, mp_obj_t bytes) { - lcd_cec_send_frame(dst_addr, src_addr, bytes); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(py_lcd_send_frame_obj, py_lcd_send_frame); - -STATIC mp_obj_t py_lcd_receive_frame(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - return lcd_cec_receive_frame(n_args, args, kw_args); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_lcd_receive_frame_obj, 1, py_lcd_receive_frame); - -STATIC mp_obj_t py_lcd_register_cec_receive_cb(mp_obj_t cb, mp_obj_t dst_addr) { - lcd_cec_register_cec_receive_cb(cb, dst_addr); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_lcd_register_cec_receive_cb_obj, py_lcd_register_cec_receive_cb); - -STATIC mp_obj_t py_lcd_received_frame_src_addr() { - return lcd_cec_received_frame_src_addr(); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_received_frame_src_addr_obj, py_lcd_received_frame_src_addr); - -STATIC mp_obj_t py_lcd_received_frame_bytes() { - return lcd_cec_received_frame_bytes(); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_received_frame_bytes_obj, py_lcd_received_frame_bytes); -#endif - -#ifdef OMV_TOUCH_PRESENT -STATIC mp_obj_t py_lcd_update_touch_points() { - return lcd_touch_update_touch_points(); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_update_touch_points_obj, py_lcd_update_touch_points); - -STATIC mp_obj_t py_lcd_register_touch_cb(mp_obj_t cb) { - lcd_touch_register_touch_cb(cb); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_lcd_register_touch_cb_obj, py_lcd_register_touch_cb); - -STATIC mp_obj_t py_lcd_get_gesture() { - return lcd_touch_get_gesture(); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_get_gesture_obj, py_lcd_get_gesture); - -STATIC mp_obj_t py_lcd_get_points() { - return lcd_touch_get_points(); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_lcd_get_points_obj, py_lcd_get_points); - -STATIC mp_obj_t py_lcd_get_point_flag(mp_obj_t index) { - return lcd_touch_get_point_flag(index); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_lcd_get_point_flag_obj, py_lcd_get_point_flag); - -STATIC mp_obj_t py_lcd_get_point_id(mp_obj_t index) { - return lcd_touch_get_point_id(index); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_lcd_get_point_id_obj, py_lcd_get_point_id); - -STATIC mp_obj_t py_lcd_get_point_x_position(mp_obj_t index) { - return lcd_touch_get_point_x_position(index); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_lcd_get_point_x_position_obj, py_lcd_get_point_x_position); - -STATIC mp_obj_t py_lcd_get_point_y_position(mp_obj_t index) { - return lcd_touch_get_point_y_position(index); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_lcd_get_point_y_position_obj, py_lcd_get_point_y_position); -#endif - -STATIC mp_obj_t py_lcd_display(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) { - image_t *arg_img = py_image_cobj(args[0]); - - int arg_x_off = 0; - int arg_y_off = 0; - uint offset = 1; - if (n_args > 1) { - if (MP_OBJ_IS_TYPE(args[1], &mp_type_tuple) || MP_OBJ_IS_TYPE(args[1], &mp_type_list)) { - mp_obj_t *arg_vec; - mp_obj_get_array_fixed_n(args[1], 2, &arg_vec); - arg_x_off = mp_obj_get_int(arg_vec[0]); - arg_y_off = mp_obj_get_int(arg_vec[1]); - offset = 2; - } else if (n_args > 2) { - arg_x_off = mp_obj_get_int(args[1]); - arg_y_off = mp_obj_get_int(args[2]); - offset = 3; - } else if (n_args > 1) { - mp_raise_msg(&mp_type_TypeError, MP_ERROR_TEXT("Expected x and y offset!")); - } - } - - float arg_x_scale = 1.f; - bool got_x_scale = py_helper_keyword_float_maybe(n_args, - args, - offset + 0, - kw_args, - MP_OBJ_NEW_QSTR(MP_QSTR_x_scale), - &arg_x_scale); - - float arg_y_scale = 1.f; - bool got_y_scale = py_helper_keyword_float_maybe(n_args, - args, - offset + 1, - kw_args, - MP_OBJ_NEW_QSTR(MP_QSTR_y_scale), - &arg_y_scale); - - rectangle_t arg_roi; - py_helper_keyword_rectangle_roi(arg_img, n_args, args, offset + 2, kw_args, &arg_roi); - - int arg_rgb_channel = py_helper_keyword_int(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_rgb_channel), -1); - if ((arg_rgb_channel < -1) || (2 < arg_rgb_channel)) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("-1 <= rgb_channel <= 2!")); - } - - int arg_alpha = py_helper_keyword_int(n_args, args, offset + 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha), 256); - if ((arg_alpha < 0) || (256 < arg_alpha)) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= alpha <= 256!")); - } - - const uint16_t *color_palette = py_helper_keyword_color_palette(n_args, args, offset + 5, kw_args, NULL); - const uint8_t *alpha_palette = py_helper_keyword_alpha_palette(n_args, args, offset + 6, kw_args, NULL); - - image_hint_t hint = py_helper_keyword_int(n_args, args, offset + 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hint), 0); - - float arg_x_size; - bool got_x_size = py_helper_keyword_float_maybe(n_args, - args, - offset + 8, - kw_args, - MP_OBJ_NEW_QSTR(MP_QSTR_x_size), - &arg_x_size); - - float arg_y_size; - bool got_y_size = py_helper_keyword_float_maybe(n_args, - args, - offset + 9, - kw_args, - MP_OBJ_NEW_QSTR(MP_QSTR_y_size), - &arg_y_size); - - if (got_x_scale && got_x_size) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either x_scale or x_size not both!")); - } - if (got_y_scale && got_y_size) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either y_scale or y_size not both!")); - } - - if (got_x_size) { - arg_x_scale = arg_x_size / arg_roi.w; - } - if (got_y_size) { - arg_y_scale = arg_y_size / arg_roi.h; - } - - if ((!got_x_scale) && (!got_x_size) && got_y_size) { - arg_x_scale = arg_y_scale; - } - if ((!got_y_scale) && (!got_y_size) && got_x_size) { - arg_y_scale = arg_x_scale; - } - - if ((!lcd_triple_buffer) && (arg_y_scale < 0)) { - mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Vertical flip requires triple buffering!")); - } - - switch (lcd_type) { - #ifdef OMV_SPI_LCD_SPI_BUS - case LCD_SHIELD: { - fb_alloc_mark(); - spi_lcd_display(arg_img, arg_x_off, arg_y_off, arg_x_scale, arg_y_scale, &arg_roi, - arg_rgb_channel, arg_alpha, color_palette, alpha_palette, hint); - fb_alloc_free_till_mark(); - break; - } - #endif - #ifdef OMV_LCD_CONTROLLER - case LCD_DISPLAY: case LCD_DISPLAY_WITH_HDMI: case LCD_DISPLAY_ONLY_HDMI: { - fb_alloc_mark(); - ltdc_display(arg_img, arg_x_off, arg_y_off, arg_x_scale, arg_y_scale, &arg_roi, - arg_rgb_channel, arg_alpha, color_palette, alpha_palette, hint); - fb_alloc_free_till_mark(); - break; - } - #endif - default: { - break; - } - } - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_lcd_display_obj, 1, py_lcd_display); - -STATIC mp_obj_t py_lcd_clear(uint n_args, const mp_obj_t *args) { - switch (lcd_type) { - #ifdef OMV_SPI_LCD_SPI_BUS - case LCD_SHIELD: { - if (n_args && mp_obj_get_int(*args)) { - // turns the display off (may not be black) - spi_lcd_clear(); - } else { - // sets the display to black (not off) - fb_alloc_mark(); - spi_lcd_display(NULL, 0, 0, 1.f, 1.f, NULL, - 0, 0, NULL, NULL, 0); - fb_alloc_free_till_mark(); - } - break; - } - #endif - #ifdef OMV_LCD_CONTROLLER - case LCD_DISPLAY: case LCD_DISPLAY_WITH_HDMI: case LCD_DISPLAY_ONLY_HDMI: { - #if defined(OMV_LCD_DISP_PIN) - if ((lcd_type == LCD_DISPLAY) && n_args && mp_obj_get_int(*args)) { - // turns the display off (may not be black) - omv_gpio_write(OMV_LCD_DISP_PIN, 0); - } else { - // sets the display to black (not off) - #else - { - #endif - ltdc_clear(); - } - break; - } - #endif - default: { - break; - } - } - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_lcd_clear_obj, 0, 1, py_lcd_clear); - -STATIC const mp_rom_map_elem_t globals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_lcd) }, - { MP_ROM_QSTR(MP_QSTR_LCD_NONE), MP_ROM_INT(LCD_NONE) }, - { MP_ROM_QSTR(MP_QSTR_LCD_SHIELD), MP_ROM_INT(LCD_SHIELD) }, - #ifdef OMV_DVI_PRESENT - { MP_ROM_QSTR(MP_QSTR_LCD_DISPLAY), MP_ROM_INT(LCD_DISPLAY) }, - { MP_ROM_QSTR(MP_QSTR_LCD_DISPLAY_WITH_HDMI), MP_ROM_INT(LCD_DISPLAY_WITH_HDMI) }, - { MP_ROM_QSTR(MP_QSTR_LCD_DISPLAY_ONLY_HDMI), MP_ROM_INT(LCD_DISPLAY_ONLY_HDMI) }, - { MP_ROM_QSTR(MP_QSTR_QVGA), MP_ROM_INT(LCD_DISPLAY_QVGA) }, - { MP_ROM_QSTR(MP_QSTR_TQVGA), MP_ROM_INT(LCD_DISPLAY_TQVGA) }, - { MP_ROM_QSTR(MP_QSTR_FHVGA), MP_ROM_INT(LCD_DISPLAY_FHVGA) }, - { MP_ROM_QSTR(MP_QSTR_FHVGA2), MP_ROM_INT(LCD_DISPLAY_FHVGA2) }, - { MP_ROM_QSTR(MP_QSTR_VGA), MP_ROM_INT(LCD_DISPLAY_VGA) }, - { MP_ROM_QSTR(MP_QSTR_THVGA), MP_ROM_INT(LCD_DISPLAY_THVGA) }, - { MP_ROM_QSTR(MP_QSTR_FWVGA), MP_ROM_INT(LCD_DISPLAY_FWVGA) }, - { MP_ROM_QSTR(MP_QSTR_FWVGA2), MP_ROM_INT(LCD_DISPLAY_FWVGA2) }, - { MP_ROM_QSTR(MP_QSTR_TFWVGA), MP_ROM_INT(LCD_DISPLAY_TFWVGA) }, - { MP_ROM_QSTR(MP_QSTR_TFWVGA2), MP_ROM_INT(LCD_DISPLAY_TFWVGA2) }, - { MP_ROM_QSTR(MP_QSTR_SVGA), MP_ROM_INT(LCD_DISPLAY_SVGA) }, - { MP_ROM_QSTR(MP_QSTR_WSVGA), MP_ROM_INT(LCD_DISPLAY_WSVGA) }, - { MP_ROM_QSTR(MP_QSTR_XGA), MP_ROM_INT(LCD_DISPLAY_XGA) }, - { MP_ROM_QSTR(MP_QSTR_SXGA), MP_ROM_INT(LCD_DISPLAY_SXGA) }, - { MP_ROM_QSTR(MP_QSTR_SXGA2), MP_ROM_INT(LCD_DISPLAY_SXGA2) }, - { MP_ROM_QSTR(MP_QSTR_UXGA), MP_ROM_INT(LCD_DISPLAY_UXGA) }, - { MP_ROM_QSTR(MP_QSTR_HD), MP_ROM_INT(LCD_DISPLAY_HD) }, - { MP_ROM_QSTR(MP_QSTR_FHD), MP_ROM_INT(LCD_DISPLAY_FHD) }, - #endif - #ifdef OMV_TOUCH_PRESENT - { MP_ROM_QSTR(MP_QSTR_LCD_GESTURE_MOVE_UP), MP_ROM_INT(PY_LCD_TOUCH_GESTURE_MOVE_UP) }, - { MP_ROM_QSTR(MP_QSTR_LCD_GESTURE_MOVE_LEFT), MP_ROM_INT(PY_LCD_TOUCH_GESTURE_MOVE_LEFT) }, - { MP_ROM_QSTR(MP_QSTR_LCD_GESTURE_MOVE_DOWN), MP_ROM_INT(PY_LCD_TOUCH_GESTURE_MOVE_DOWN) }, - { MP_ROM_QSTR(MP_QSTR_LCD_GESTURE_MOVE_RIGHT), MP_ROM_INT(PY_LCD_TOUCH_GESTURE_MOVE_RIGHT) }, - { MP_ROM_QSTR(MP_QSTR_LCD_GESTURE_ZOOM_IN), MP_ROM_INT(PY_LCD_TOUCH_GESTURE_ZOOM_IN) }, - { MP_ROM_QSTR(MP_QSTR_LCD_GESTURE_ZOOM_OUT), MP_ROM_INT(PY_LCD_TOUCH_GESTURE_ZOOM_OUT) }, - { MP_ROM_QSTR(MP_QSTR_LCD_GESTURE_NONE), MP_ROM_INT(PY_LCD_TOUCH_GESTURE_NONE) }, - { MP_ROM_QSTR(MP_QSTR_LCD_FLAG_PRESSED), MP_ROM_INT(PY_LCD_TOUCH_EVENT_PUT_DOWN) }, - { MP_ROM_QSTR(MP_QSTR_LCD_FLAG_RELEASED), MP_ROM_INT(PY_LCD_TOUCH_EVENT_PUT_UP) }, - { MP_ROM_QSTR(MP_QSTR_LCD_FLAG_MOVED), MP_ROM_INT(PY_LCD_TOUCH_EVENT_CONTACT) }, - #endif - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&py_lcd_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&py_lcd_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_lcd_width_obj) }, - { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_lcd_height_obj) }, - { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&py_lcd_type_obj) }, - { MP_ROM_QSTR(MP_QSTR_triple_buffer), MP_ROM_PTR(&py_lcd_triple_buffer_obj) }, - { MP_ROM_QSTR(MP_QSTR_bgr), MP_ROM_PTR(&py_lcd_bgr_obj) }, - { MP_ROM_QSTR(MP_QSTR_byte_reverse), MP_ROM_PTR(&py_lcd_byte_reverse_obj) }, - { MP_ROM_QSTR(MP_QSTR_framesize), MP_ROM_PTR(&py_lcd_framesize_obj) }, - { MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&py_lcd_refresh_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_backlight), MP_ROM_PTR(&py_lcd_get_backlight_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_backlight), MP_ROM_PTR(&py_lcd_set_backlight_obj) }, - #ifdef OMV_DVI_PRESENT - { MP_ROM_QSTR(MP_QSTR_get_display_connected), MP_ROM_PTR(&py_lcd_get_display_connected_obj) }, - { MP_ROM_QSTR(MP_QSTR_register_hotplug_cb), MP_ROM_PTR(&py_lcd_register_hotplug_cb_obj) }, - #else - { MP_ROM_QSTR(MP_QSTR_get_display_connected), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_register_hotplug_cb), MP_ROM_PTR(&py_func_unavailable_obj) }, - #endif - #if defined(OMV_DVI_PRESENT) && defined(OMV_DDC_PRESENT) - { MP_ROM_QSTR(MP_QSTR_get_display_id_data), MP_ROM_PTR(&py_lcd_get_display_id_data_obj) }, - #else - { MP_ROM_QSTR(MP_QSTR_get_display_id_data), MP_ROM_PTR(&py_func_unavailable_obj) }, - #endif - #if defined(OMV_DVI_PRESENT) && defined(OMV_CEC_PRESENT) - { MP_ROM_QSTR(MP_QSTR_send_frame), MP_ROM_PTR(&py_lcd_send_frame_obj) }, - { MP_ROM_QSTR(MP_QSTR_receive_frame), MP_ROM_PTR(&py_lcd_receive_frame_obj) }, - { MP_ROM_QSTR(MP_QSTR_register_receive_cb), MP_ROM_PTR(&py_lcd_register_cec_receive_cb_obj) }, - { MP_ROM_QSTR(MP_QSTR_received_frame_src_addr), MP_ROM_PTR(&py_lcd_received_frame_src_addr_obj) }, - { MP_ROM_QSTR(MP_QSTR_received_frame_bytes), MP_ROM_PTR(&py_lcd_received_frame_bytes_obj) }, - #else - { MP_ROM_QSTR(MP_QSTR_send_frame), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_receive_frame), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_register_receive_cb), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_received_frame_src_addr), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_received_frame_bytes), MP_ROM_PTR(&py_func_unavailable_obj) }, - #endif - #ifdef OMV_TOUCH_PRESENT - { MP_ROM_QSTR(MP_QSTR_update_touch_points), MP_ROM_PTR(&py_lcd_update_touch_points_obj) }, - { MP_ROM_QSTR(MP_QSTR_register_touch_cb), MP_ROM_PTR(&py_lcd_register_touch_cb_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_gesture), MP_ROM_PTR(&py_lcd_get_gesture_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_points), MP_ROM_PTR(&py_lcd_get_points_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_point_flag), MP_ROM_PTR(&py_lcd_get_point_flag_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_point_id), MP_ROM_PTR(&py_lcd_get_point_id_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_point_x_position), MP_ROM_PTR(&py_lcd_get_point_x_position_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_point_y_position), MP_ROM_PTR(&py_lcd_get_point_y_position_obj) }, - #else - { MP_ROM_QSTR(MP_QSTR_update_touch_points), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_register_touch_cb), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_gesture), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_points), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_point_flag), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_point_id), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_point_x_position), MP_ROM_PTR(&py_func_unavailable_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_point_y_position), MP_ROM_PTR(&py_func_unavailable_obj) }, - #endif - { MP_ROM_QSTR(MP_QSTR_display), MP_ROM_PTR(&py_lcd_display_obj) }, - { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_lcd_clear_obj) }, -}; - -STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); - -const mp_obj_module_t lcd_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_t) &globals_dict, -}; - -void py_lcd_init0() { - py_lcd_deinit(); -} - -MP_REGISTER_MODULE(MP_QSTR_lcd, lcd_module); - -#endif // MICROPY_PY_LCD diff --git a/src/omv/modules/py_lcd.h b/src/omv/modules/py_lcd.h deleted file mode 100644 index 20c997f53..000000000 --- a/src/omv/modules/py_lcd.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * This file is part of the OpenMV project. - * - * Copyright (c) 2013-2021 Ibrahim Abdelkader - * Copyright (c) 2013-2021 Kwabena W. Agyeman - * - * This work is licensed under the MIT license, see the file LICENSE for details. - * - * LCD Python module. - */ -#ifndef __PY_LCD_H__ -#define __PY_LCD_H__ -void py_lcd_init0(); -#endif // __PY_LCD_H__ diff --git a/src/omv/modules/py_spi_display.c b/src/omv/modules/py_spi_display.c new file mode 100644 index 000000000..a1a68f09b --- /dev/null +++ b/src/omv/modules/py_spi_display.c @@ -0,0 +1,454 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * SPI Display Python module. + */ +#include "omv_boardconfig.h" + +#if MICROPY_PY_DISPLAY && defined(OMV_SPI_DISPLAY_CONTROLLER) + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/runtime.h" +#include "mphal.h" + +#include "py_image.h" +#include "omv_gpio.h" +#include "omv_spi.h" +#include "py_display.h" + +#define LCD_COMMAND_DISPOFF (0x28) +#define LCD_COMMAND_DISPON (0x29) +#define LCD_COMMAND_RAMWR (0x2C) +#define LCD_COMMAND_SLPOUT (0x11) +#define LCD_COMMAND_MADCTL (0x36) +#define LCD_COMMAND_COLMOD (0x3A) + +#if OMV_SPI_DISPLAY_TRIPLE_BUFFER +#define LCD_TRIPLE_BUFFER_DEFAULT (true) +#else +#define LCD_TRIPLE_BUFFER_DEFAULT (false) +#endif + +static void spi_transmit(py_display_obj_t *self, uint8_t *txdata, uint16_t size) { + omv_spi_transfer_t spi_xfer = { + .txbuf = txdata, + .size = size, + .timeout = OMV_SPI_MAX_TIMEOUT, + .flags = OMV_SPI_XFER_BLOCKING + }; + + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); + omv_spi_transfer_start(&self->spi_bus, &spi_xfer); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); +} + +static void spi_transmit_16(py_display_obj_t *self, uint8_t *txdata, uint16_t size) { + omv_spi_transfer_t spi_xfer = { + .txbuf = txdata, + .size = (!self->byte_swap) ? size : (size * 2), + .timeout = OMV_SPI_MAX_TIMEOUT, + .flags = OMV_SPI_XFER_BLOCKING, + }; + + omv_spi_transfer_start(&self->spi_bus, &spi_xfer); +} + +static void spi_switch_mode(py_display_obj_t *self, int bits, bool dma) { + omv_spi_deinit(&self->spi_bus); + + omv_spi_config_t spi_config; + omv_spi_default_config(&spi_config, OMV_SPI_DISPLAY_CONTROLLER); + + spi_config.baudrate = self->spi_baudrate; + spi_config.datasize = bits; + spi_config.bus_mode = OMV_SPI_BUS_TX; + spi_config.nss_enable = false; + spi_config.dma_flags = dma ? OMV_SPI_DMA_NORMAL : 0; + omv_spi_init(&self->spi_bus, &spi_config); +} + +static void spi_display_command(py_display_obj_t *self, uint8_t cmd, uint8_t arg) { + omv_gpio_write(OMV_SPI_DISPLAY_RS_PIN, 0); + spi_transmit(self, (uint8_t []) { cmd }, 1); + omv_gpio_write(OMV_SPI_DISPLAY_RS_PIN, 1); + if (arg) { + spi_transmit(self, (uint8_t []) { arg }, 1); + } +} + +static void spi_display_callback(omv_spi_t *spi, void *userdata, void *buf) { + py_display_obj_t *self = (py_display_obj_t *) userdata; + + static uint8_t *spi_state_write_addr = NULL; + static size_t spi_state_write_count = 0; + + // If userdata is not null then it means that we are being kicked off. + if (buf == NULL) { + spi_state_write_count = 0; + } + + if (!spi_state_write_count) { + spi_state_write_addr = (uint8_t *) self->framebuffers[self->framebuffer_tail]; + spi_state_write_count = self->width * self->height; + + if (self->byte_swap) { + spi_state_write_count *= 2; + } + + self->framebuffer_head = self->framebuffer_tail; + } + + size_t spi_state_write_limit = (!self->byte_swap) ? OMV_SPI_MAX_16BIT_XFER : OMV_SPI_MAX_8BIT_XFER; + uint8_t *addr = spi_state_write_addr; + size_t count = IM_MIN(spi_state_write_count, spi_state_write_limit); + + spi_state_write_addr += (!self->byte_swap) ? (count * 2) : count; + spi_state_write_count -= count; + + // When starting the interrupt chain the first transfer is not executed in interrupt context. + // So, disable interrupts for the first transfer so that it completes first and unlocks the + // SPI bus before allowing the interrupt it causes to trigger starting the interrupt chain. + omv_spi_transfer_t spi_xfer = { + .txbuf = addr, + .size = count, + .flags = OMV_SPI_XFER_DMA, + .userdata = self, + .callback = spi_display_callback, + }; + + if (buf == NULL) { + uint32_t irq_state = disable_irq(); + omv_spi_transfer_start(&self->spi_bus, &spi_xfer); + enable_irq(irq_state); + } else { + omv_spi_transfer_start(&self->spi_bus, &spi_xfer); + } +} + +static void spi_display_kick(py_display_obj_t *self) { + if (!self->spi_tx_running) { + spi_display_command(self, LCD_COMMAND_RAMWR, 0); + spi_switch_mode(self, (!self->byte_swap) ? 16 : 8, true); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); + + // Limit the transfer size to single lines as you cannot send more + // than 64KB per SPI transaction generally. + for (int i = 0; i < self->height; i++) { + uint8_t *buffer = (uint8_t *) (self->framebuffers[self->framebuffer_tail] + (self->width * i)); + spi_transmit_16(self, buffer, self->width); + } + + spi_switch_mode(self, 8, false); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + spi_display_command(self, LCD_COMMAND_DISPON, 0); + spi_display_command(self, LCD_COMMAND_RAMWR, 0); + spi_switch_mode(self, (!self->byte_swap) ? 16 : 8, true); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); + + // Kickoff interrupt driven image update. + self->spi_tx_running = true; + spi_display_callback(&self->spi_bus, self, NULL); + } +} + +// TODO remove this when imlib_draw_image accepts a context. +static py_display_obj_t *lcd_self; + +static void spi_display_draw_image_cb(int x_start, int x_end, int y_row, imlib_draw_row_data_t *data) { + spi_transmit_16(lcd_self, data->dst_row_override, lcd_self->width); +} + +static void spi_display_write(py_display_obj_t *self, image_t *src_img, int dst_x_start, int dst_y_start, + float x_scale, float y_scale, rectangle_t *roi, int rgb_channel, int alpha, + const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint) { + image_t dst_img; + dst_img.w = self->width; + dst_img.h = self->height; + dst_img.pixfmt = PIXFORMAT_RGB565; + + int x0, x1, y0, y1; + bool black = !imlib_draw_image_rectangle(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, + y_scale, roi, alpha, alpha_palette, hint, &x0, &x1, &y0, &y1); + + if (!self->triple_buffer) { + dst_img.data = fb_alloc0(self->width * sizeof(uint16_t), FB_ALLOC_NO_HINT); + + spi_display_command(self, LCD_COMMAND_RAMWR, 0); + spi_switch_mode(self, (!self->byte_swap) ? 16 : 8, true); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); + + if (black) { + // zero the whole image + for (int i = 0; i < self->height; i++) { + spi_transmit_16(self, dst_img.data, self->width); + } + } else { + // Zero the top rows + for (int i = 0; i < y0; i++) { + spi_transmit_16(self, dst_img.data, self->width); + } + + // TODO remove this when imlib_draw_image accepts a context. + lcd_self = self; + + // Transmits left/right parts already zeroed... + imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, + x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, + hint | IMAGE_HINT_BLACK_BACKGROUND, spi_display_draw_image_cb, dst_img.data); + // Zero the bottom rows + if (y1 < self->height) { + memset(dst_img.data, 0, self->width * sizeof(uint16_t)); + } + + for (int i = y1; i < self->height; i++) { + spi_transmit_16(self, dst_img.data, self->width); + } + } + + spi_switch_mode(self, 8, false); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + spi_display_command(self, LCD_COMMAND_DISPON, 0); + fb_free(); + } else { + // For triple buffering we are never drawing where tail or head + // (which may instantly update to to be equal to tail) is. + int new_framebuffer_tail = (self->framebuffer_tail + 1) % FRAMEBUFFER_COUNT; + if (new_framebuffer_tail == self->framebuffer_head) { + new_framebuffer_tail = (new_framebuffer_tail + 1) % FRAMEBUFFER_COUNT; + } + dst_img.data = (uint8_t *) self->framebuffers[new_framebuffer_tail]; + + if (black) { + // zero the whole image + memset(dst_img.data, 0, self->width * self->height * sizeof(uint16_t)); + } else { + // Zero the top rows + if (y0) { + memset(dst_img.data, 0, self->width * y0 * sizeof(uint16_t)); + } + + if (x0) { + for (int i = y0; i < y1; i++) { + // Zero left + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i), 0, x0 * sizeof(uint16_t)); + } + } + + imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, + x_scale, y_scale, roi, rgb_channel, alpha, color_palette, + alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL); + + if (self->width - x1) { + for (int i = y0; i < y1; i++) { + // Zero right + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, i) + x1, 0, (self->width - x1) * sizeof(uint16_t)); + } + } + + // Zero the bottom rows + if (self->height - y1) { + memset(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, y1), + 0, self->width * (self->height - y1) * sizeof(uint16_t)); + } + } + + #ifdef __DCACHE_PRESENT + // Flush data for DMA + SCB_CleanDCache_by_Addr((uint32_t *) dst_img.data, image_size(&dst_img)); + #endif + + // Update tail which means a new image is ready. + self->framebuffer_tail = new_framebuffer_tail; + + // Kick off an update of the display. + spi_display_kick(self); + } +} + +static void spi_display_clear(py_display_obj_t *self, bool display_off) { + if (display_off) { + // turns the display off (may not be black) + if (self->spi_tx_running) { + omv_spi_transfer_abort(&self->spi_bus); + self->spi_tx_running = false; + spi_switch_mode(self, 8, false); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + } + } else { + spi_display_command(self, LCD_COMMAND_DISPOFF, 0); + fb_alloc_mark(); + spi_display_write(self, NULL, 0, 0, 1.f, 1.f, NULL, 0, 0, NULL, NULL, 0); + fb_alloc_free_till_mark(); + } +} + +#ifdef OMV_DISPLAY_BL_DAC +static DAC_HandleTypeDef lcd_dac_handle; +#endif + +#ifdef OMV_SPI_DISPLAY_BL_PIN +static void spi_display_set_backlight(py_display_obj_t *self, uint32_t intensity) { + #ifdef OMV_DISPLAY_BL_DAC + if ((self->intensity < 255) && (255 <= intensity)) { + #else + if ((self->intensity < 1) && (1 <= intensity)) { + #endif + omv_gpio_write(OMV_SPI_DISPLAY_BL_PIN, 1); + omv_gpio_deinit(OMV_SPI_DISPLAY_BL_PIN); + } else if ((0 < self->intensity) && (intensity <= 0)) { + omv_gpio_config(OMV_SPI_DISPLAY_BL_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_SPI_DISPLAY_BL_PIN, 0); + } + + #ifdef OMV_DISPLAY_BL_DAC + if (((self->intensity <= 0) || (255 <= self->intensity)) && (0 < intensity) && (intensity < 255)) { + omv_gpio_config(OMV_SPI_DISPLAY_BL_PIN, OMV_GPIO_MODE_ANALOG, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + + DAC_ChannelConfTypeDef lcd_dac_channel_handle; + lcd_dac_handle.Instance = OMV_DISPLAY_BL_DAC; + lcd_dac_channel_handle.DAC_Trigger = DAC_TRIGGER_NONE; + lcd_dac_channel_handle.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; + #if defined(MCU_SERIES_H7) + lcd_dac_channel_handle.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE; + lcd_dac_channel_handle.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_DISABLE; + lcd_dac_channel_handle.DAC_UserTrimming = DAC_TRIMMING_FACTORY; + #endif + + HAL_DAC_Init(&lcd_dac_handle); + HAL_DAC_ConfigChannel(&lcd_dac_handle, &lcd_dac_channel_handle, OMV_DISPLAY_BL_DAC_CHANNEL); + HAL_DAC_Start(&lcd_dac_handle, OMV_DISPLAY_BL_DAC_CHANNEL); + HAL_DAC_SetValue(&lcd_dac_handle, OMV_DISPLAY_BL_DAC_CHANNEL, DAC_ALIGN_8B_R, intensity); + } else if ((0 < self->intensity) && (self->intensity < 255) && ((intensity <= 0) || (255 <= intensity))) { + HAL_DAC_Stop(&lcd_dac_handle, OMV_DISPLAY_BL_DAC_CHANNEL); + HAL_DAC_DeInit(&lcd_dac_handle); + } else if ((0 < self->intensity) && (self->intensity < 255) && (0 < intensity) && (intensity < 255)) { + HAL_DAC_SetValue(&lcd_dac_handle, OMV_DISPLAY_BL_DAC_CHANNEL, DAC_ALIGN_8B_R, intensity); + } + #endif + + self->intensity = intensity; +} +#endif // OMV_SPI_DISPLAY_BL_PIN + +static void spi_display_deinit(py_display_obj_t *self) { + if (self->triple_buffer) { + omv_spi_transfer_abort(&self->spi_bus); + fb_alloc_free_till_mark_past_mark_permanent(); + } + + omv_spi_deinit(&self->spi_bus); + omv_gpio_deinit(OMV_SPI_DISPLAY_RS_PIN); + omv_gpio_deinit(OMV_SPI_DISPLAY_RST_PIN); + + #ifdef OMV_SPI_DISPLAY_BL_PIN + spi_display_set_backlight(self, 255); + #endif +} + +mp_obj_t spi_display_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_width, ARG_height, ARG_refresh, ARG_triple_buffer, ARG_bgr, ARG_byte_swap}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_width, MP_ARG_INT, {.u_int = 128 } }, + { MP_QSTR_height, MP_ARG_INT, {.u_int = 160 } }, + { MP_QSTR_refresh, MP_ARG_INT, {.u_int = 60 } }, + { MP_QSTR_bgr, MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_byte_swap, MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_triple_buffer, MP_ARG_BOOL, {.u_bool = LCD_TRIPLE_BUFFER_DEFAULT} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if ((args[ARG_width].u_int <= 0) || (args[ARG_width].u_int > 32767)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Width!")); + } + if ((args[ARG_height].u_int <= 0) || (args[ARG_height].u_int > 32767)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Height!")); + } + if ((args[ARG_refresh].u_int < 30) || (args[ARG_refresh].u_int > 120)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Refresh Rate!")); + } + + py_display_obj_t *self = m_new_obj_with_finaliser(py_display_obj_t); + self->base.type = &py_spi_display_type; + self->framebuffer_tail = 0; + self->framebuffer_head = 0; + self->width = args[ARG_width].u_int; + self->height = args[ARG_height].u_int; + self->refresh = args[ARG_refresh].u_int; + self->triple_buffer = args[ARG_triple_buffer].u_bool; + self->bgr = args[ARG_bgr].u_bool; + self->byte_swap = args[ARG_byte_swap].u_bool; + + omv_spi_config_t spi_config; + omv_spi_default_config(&spi_config, OMV_SPI_DISPLAY_CONTROLLER); + + self->spi_baudrate = self->width * self->height * self->refresh * 16; + spi_config.baudrate = self->spi_baudrate; + spi_config.bus_mode = OMV_SPI_BUS_TX; + spi_config.nss_enable = false; + omv_spi_init(&self->spi_bus, &spi_config); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); + + omv_gpio_config(OMV_SPI_DISPLAY_RST_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_SPI_DISPLAY_RST_PIN, 1); + + omv_gpio_config(OMV_SPI_DISPLAY_RS_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_SPI_DISPLAY_RS_PIN, 1); + + // Reset LCD + omv_gpio_write(OMV_SPI_DISPLAY_RST_PIN, 0); + mp_hal_delay_ms(100); + omv_gpio_write(OMV_SPI_DISPLAY_RST_PIN, 1); + mp_hal_delay_ms(100); + + // Sleep out + spi_display_command(self, LCD_COMMAND_SLPOUT, 0); + mp_hal_delay_ms(120); + // Memory data access control + spi_display_command(self, LCD_COMMAND_MADCTL, self->bgr ? 0xC8 : 0xC0); + // Interface pixel format + spi_display_command(self, LCD_COMMAND_COLMOD, 0x05); + + if (self->triple_buffer) { + fb_alloc_mark(); + uint32_t fb_size = self->width * self->height * sizeof(uint16_t); + for (int i = 0; i < FRAMEBUFFER_COUNT; i++) { + self->framebuffers[i] = (uint16_t *) fb_alloc0(fb_size, FB_ALLOC_CACHE_ALIGN); + } + fb_alloc_mark_permanent(); + } + + #ifdef OMV_SPI_DISPLAY_BL_PIN + spi_display_set_backlight(self, 255); + #endif + + return MP_OBJ_FROM_PTR(self); +} + +STATIC const py_display_p_t py_display_p = { + .deinit = spi_display_deinit, + .clear = spi_display_clear, + .write = spi_display_write, + #ifdef OMV_SPI_DISPLAY_BL_PIN + .set_backlight = spi_display_set_backlight, + #endif +}; + +MP_DEFINE_CONST_OBJ_TYPE( + py_spi_display_type, + MP_QSTR_SPIDisplay, + MP_TYPE_FLAG_NONE, + make_new, spi_display_make_new, + protocol, &py_display_p, + locals_dict, &py_display_locals_dict + ); + +#endif // MICROPY_PY_DISPLAY diff --git a/src/omv/modules/py_tv.c b/src/omv/modules/py_tv.c index e74be6b5d..deb340171 100644 --- a/src/omv/modules/py_tv.c +++ b/src/omv/modules/py_tv.c @@ -34,7 +34,7 @@ #error "TV_HEIGHT not even" #endif -#ifdef OMV_SPI_LCD_SPI_BUS +#ifdef OMV_SPI_DISPLAY_CONTROLLER ///////////////////////////////////////////////////////////// // http://www.vsdsp-forum.com/phpbb/viewtopic.php?f=14&t=1801 ///////////////////////////////////////////////////////////// @@ -219,11 +219,11 @@ static void SpiTransmitReceivePacket(uint8_t *txdata, uint8_t *rxdata, uint16_t .flags = OMV_SPI_XFER_BLOCKING }; - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 0); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); omv_spi_transfer_start(&spi_bus, &spi_xfer); if (end) { - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 1); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); } } @@ -395,7 +395,7 @@ tv_type = TV_NONE; static bool tv_triple_buffer = false; -#ifdef OMV_SPI_LCD_SPI_BUS +#ifdef OMV_SPI_DISPLAY_CONTROLLER static volatile enum { SPI_TX_CB_IDLE, SPI_TX_CB_MEMORY_WRITE_CMD, @@ -415,14 +415,14 @@ static void spi_config_deinit() { static void spi_config_init(bool triple_buffer) { omv_spi_config_t spi_config; - omv_spi_default_config(&spi_config, OMV_SPI_LCD_SPI_BUS); + omv_spi_default_config(&spi_config, OMV_SPI_DISPLAY_CONTROLLER); spi_config.baudrate = TV_BAUDRATE; spi_config.nss_enable = false; spi_config.dma_flags = triple_buffer ? OMV_SPI_DMA_NORMAL : 0; omv_spi_init(&spi_bus, &spi_config); - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 1); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); SpiRamVideoInit(); @@ -458,12 +458,12 @@ static void spi_tv_callback(omv_spi_t *spi, void *userdata, void *buf) { switch (spi_tx_cb_state) { case SPI_TX_CB_MEMORY_WRITE_CMD: { - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 1); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); spi_tx_cb_state = SPI_TX_CB_MEMORY_WRITE; spi_tx_cb_state_memory_write_addr = (uint8_t *) framebuffers[framebuffer_head]; spi_tx_cb_state_memory_write_count = PICLINE_LENGTH_BYTES * TV_HEIGHT; framebuffer_tail = framebuffer_head; - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 0); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 0); // When starting the interrupt chain the first transfer is not executed // in interrupt context. So, disable interrupts for the first transfer so // that it completes first and unlocks the SPI bus before allowing the interrupt @@ -621,7 +621,7 @@ static void spi_tv_display(image_t *src_img, int dst_x_start, int dst_y_start, f } } - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 1); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); fb_free(); } else { // For triple buffering we are never drawing where head or tail (which may instantly update to @@ -736,7 +736,7 @@ static void spi_tv_display(image_t *src_img, int dst_x_start, int dst_y_start, f STATIC mp_obj_t py_tv_deinit() { switch (tv_type) { - #ifdef OMV_SPI_LCD_SPI_BUS + #ifdef OMV_SPI_DISPLAY_CONTROLLER case TV_SHIELD: { spi_config_deinit(); break; @@ -760,11 +760,11 @@ STATIC mp_obj_t py_tv_init(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) MP_OBJ_NEW_QSTR(MP_QSTR_type), TV_SHIELD); switch (type) { - #ifdef OMV_SPI_LCD_SPI_BUS + #ifdef OMV_SPI_DISPLAY_CONTROLLER case TV_SHIELD: { bool triple_buffer_def = false; - #ifdef OMV_SPI_LCD_DEF_TRIPLE_BUF - triple_buffer_def = OMV_SPI_LCD_DEF_TRIPLE_BUF; + #ifdef OMV_SPI_DISPLAY_TRIPLE_BUFFER + triple_buffer_def = OMV_SPI_DISPLAY_TRIPLE_BUFFER; #endif bool triple_buffer = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_triple_buffer), triple_buffer_def); @@ -833,11 +833,11 @@ STATIC mp_obj_t py_tv_channel(uint n_args, const mp_obj_t *args) { return mp_const_none; } - #ifdef OMV_SPI_LCD_SPI_BUS + #ifdef OMV_SPI_DISPLAY_CONTROLLER if (tv_triple_buffer) { omv_spi_transfer_abort(&spi_bus); spi_tx_cb_state = SPI_TX_CB_IDLE; - omv_gpio_write(OMV_SPI_LCD_SSEL_PIN, 1); + omv_gpio_write(OMV_SPI_DISPLAY_SSEL_PIN, 1); } if (n_args) { @@ -849,11 +849,11 @@ STATIC mp_obj_t py_tv_channel(uint n_args, const mp_obj_t *args) { SpiRamWriteByteRegister(WRITE_GPIO, 0x70 | (channel - 1)); } else { - #ifdef OMV_SPI_LCD_RX_CLK_DIV - omv_spi_set_baudrate(&spi_bus, TV_BAUDRATE / OMV_SPI_LCD_RX_CLK_DIV); + #ifdef OMV_SPI_DISPLAY_RX_CLK_DIV + omv_spi_set_baudrate(&spi_bus, TV_BAUDRATE / OMV_SPI_DISPLAY_RX_CLK_DIV); #endif int channel = SpiRamReadByteRegister(READ_GPIO); - #ifdef OMV_SPI_LCD_RX_CLK_DIV + #ifdef OMV_SPI_DISPLAY_RX_CLK_DIV omv_spi_set_baudrate(&spi_bus, TV_BAUDRATE); #endif return mp_obj_new_int((channel & 0x7) + 1); @@ -949,7 +949,7 @@ STATIC mp_obj_t py_tv_display(uint n_args, const mp_obj_t *args, mp_map_t *kw_ar } switch (tv_type) { - #ifdef OMV_SPI_LCD_SPI_BUS + #ifdef OMV_SPI_DISPLAY_CONTROLLER case TV_SHIELD: { fb_alloc_mark(); spi_tv_display(arg_img, arg_x_off, arg_y_off, arg_x_scale, arg_y_scale, &arg_roi, @@ -969,7 +969,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tv_display_obj, 1, py_tv_display); STATIC mp_obj_t py_tv_clear() { switch (tv_type) { - #ifdef OMV_SPI_LCD_SPI_BUS + #ifdef OMV_SPI_DISPLAY_CONTROLLER case TV_SHIELD: { fb_alloc_mark(); spi_tv_display(NULL, 0, 0, 1.f, 1.f, NULL, diff --git a/src/omv/ports/mimxrt/main.c b/src/omv/ports/mimxrt/main.c index 6d4a84522..098299313 100644 --- a/src/omv/ports/mimxrt/main.c +++ b/src/omv/ports/mimxrt/main.c @@ -28,7 +28,6 @@ #include "systick.h" #include "modmimxrt.h" -#include "py_lcd.h" #include "py_fir.h" #include "py_tv.h" @@ -115,9 +114,6 @@ soft_reset: mp_init(); // Initialise low-level sub-systems. - #if MICROPY_PY_LCD - py_lcd_init0(); - #endif py_fir_init0(); #if MICROPY_PY_TV py_tv_init0(); diff --git a/src/omv/ports/stm32/main.c b/src/omv/ports/stm32/main.c index d1b494cad..635469a20 100644 --- a/src/omv/ports/stm32/main.c +++ b/src/omv/ports/stm32/main.c @@ -67,7 +67,6 @@ #include "usbd_cdc_interface.h" #include "py_image.h" -#include "py_lcd.h" #include "py_fir.h" #include "py_tv.h" #include "py_buzzer.h" @@ -359,9 +358,6 @@ soft_reset: // Initialise low-level sub-systems. Here we need to do the very basic // things like zeroing out memory and resetting any of the sub-systems. - #if MICROPY_PY_LCD - py_lcd_init0(); - #endif py_fir_init0(); #if MICROPY_PY_TV py_tv_init0(); diff --git a/src/omv/ports/stm32/modules/py_display.c b/src/omv/ports/stm32/modules/py_display.c new file mode 100644 index 000000000..bcbb2bcb2 --- /dev/null +++ b/src/omv/ports/stm32/modules/py_display.c @@ -0,0 +1,510 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * LTDC display Python module. + */ +#include "omv_boardconfig.h" + +#if MICROPY_PY_DISPLAY && defined(OMV_RGB_DISPLAY_CONTROLLER) + +#include "py/obj.h" +#include "py/runtime.h" +#include "mphal.h" + +#include "py_helper.h" +#include "py_image.h" +#include "omv_gpio.h" +#include "py_display.h" + +#if defined(OMV_RGB_DISPLAY_BL_PIN) +#define OMV_DISPLAY_BL_PIN OMV_RGB_DISPLAY_BL_PIN +#endif + +#if defined(OMV_RGB_DISPLAY_DISP_PIN) +#define OMV_DISPLAY_DISP_PIN OMV_RGB_DISPLAY_DISP_PIN +#endif + +typedef struct display_mode { + uint32_t hactive; + uint32_t vactive; + uint32_t pixel_clock; + uint32_t hsync_len; + uint32_t hback_porch; + uint32_t hfront_porch; + uint32_t vsync_len; + uint32_t vback_porch; + uint32_t vfront_porch; + uint32_t hpol : 1; + uint32_t vpol : 1; +} display_mode_t; + +typedef struct _display_state { + py_display_obj_t *self; + LTDC_HandleTypeDef hltdc; + #ifdef OMV_DISPLAY_BL_TIM + TIM_HandleTypeDef htim; + #endif + LTDC_LayerCfgTypeDef framebuffer_layers[FRAMEBUFFER_COUNT]; +} display_state_t; + +static display_state_t display; + +static const display_mode_t display_modes[] = { + { // QVGA + .hactive=320, .vactive=240, .pixel_clock=6144, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=1 + }, + { // TQVGA + .hactive=240, .vactive=320, .pixel_clock=6426, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=1 + }, + { // FHVGA + .hactive=480, .vactive=272, .pixel_clock=9633, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=1 + }, + { // FHVGA2 + .hactive=480, .vactive=128, .pixel_clock=4799, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=1 + }, + { // VGA + .hactive=640, .vactive=480, .pixel_clock=21363, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=1 + }, + { // THVGA + .hactive=320, .vactive=480, .pixel_clock=11868, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=1 + }, + { // FWVGA + .hactive=800, .vactive=480, .pixel_clock=26110, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=1 + }, + { // FWVGA2 + .hactive=800, .vactive=320, .pixel_clock=17670, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=1 + }, + { // TFWVGA + .hactive=480, .vactive=800, .pixel_clock=27624, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=9 + }, + { // TFWVGA2 + .hactive=480, .vactive=480, .pixel_clock=16615, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=1 + }, + { // SVGA + .hactive=800, .vactive=600, .pixel_clock=32597, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=4 + }, + { // WSVGA + .hactive=1024, .vactive=600, .pixel_clock=40895, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=4 + }, + { // XGA + .hactive=1024, .vactive=768, .pixel_clock=52277, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=8 + }, + { // SXGA + .hactive=1280, .vactive=1024, .pixel_clock=85920, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=16 + }, + { // SXGA2 + .hactive=1280, .vactive=400, .pixel_clock=33830, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=1 + }, + { // UXGA + .hactive=1600, .vactive=1200, .pixel_clock=124364, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=21 + }, + { // HD + .hactive=1280, .vactive=720, .pixel_clock=60405, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=7 + }, + { // FHD + .hactive=1920, .vactive=1080, .pixel_clock=133187, + .hsync_len=32, .hback_porch=40, .hfront_porch=8, + .vsync_len=8, .vback_porch=6, .vfront_porch=17 + }, +}; + +void LTDC_IRQHandler() { + HAL_LTDC_IRQHandler(&display.hltdc); +} + +static void pll_config(int framesize, int refresh) { + uint32_t pixel_clock = (display_modes[framesize].pixel_clock * refresh) / 60; + + for (uint32_t divm = 1; divm <= 63; divm++) { + for (uint32_t divr = 1; divr <= 128; divr++) { + uint32_t vci = 0; + uint32_t ref_clk = (HSE_VALUE / 1000) / divm; + + if (1000 <= ref_clk && ref_clk <= 2000) { + vci = RCC_PLL3VCIRANGE_0; + } else if (2000 <= ref_clk && ref_clk <= 4000) { + vci = RCC_PLL3VCIRANGE_1; + } else if (4000 <= ref_clk && ref_clk <= 8000) { + vci = RCC_PLL3VCIRANGE_2; + } else if (8000 <= ref_clk && ref_clk <= 16000) { + vci = RCC_PLL3VCIRANGE_3; + } else { + continue; + } + + uint32_t vco = 0; + uint32_t pll_clk = pixel_clock * divr; + + if (150000 <= pll_clk && pll_clk <= 420000) { + vco = RCC_PLL3VCOMEDIUM; + } else if (192000 <= pll_clk && pll_clk <= 836000) { + vco = RCC_PLL3VCOWIDE; + } else { + continue; + } + + uint32_t divn = pll_clk / ref_clk; + if (divn < 4 || 512 < divn) { + continue; + } + + uint32_t frac = ((pll_clk % ref_clk) * 8192) / ref_clk; + + RCC_PeriphCLKInitTypeDef init; + init.PeriphClockSelection = RCC_PERIPHCLK_LTDC; + init.PLL3.PLL3M = divm; + init.PLL3.PLL3N = divn; + init.PLL3.PLL3P = 128; + init.PLL3.PLL3Q = 128; + init.PLL3.PLL3R = divr; + init.PLL3.PLL3RGE = vci; + init.PLL3.PLL3VCOSEL = vco; + init.PLL3.PLL3FRACN = frac; + + if (HAL_RCCEx_PeriphCLKConfig(&init) == HAL_OK) { + return; + } + } + } + + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unable to initialize LTDC PLL!")); +} + +static void ltdc_init(py_display_obj_t *self) { + const display_mode_t *dm = &display_modes[self->framesize]; + uint32_t fb_size = self->width * self->height * sizeof(uint16_t); + + fb_alloc_mark(); + for (int i = 0; i < FRAMEBUFFER_COUNT; i++) { + self->framebuffers[i] = (uint16_t *) fb_alloc0(fb_size, FB_ALLOC_CACHE_ALIGN); + display.framebuffer_layers[i].WindowX0 = 0; + display.framebuffer_layers[i].WindowX1 = self->width; + display.framebuffer_layers[i].WindowY0 = 0; + display.framebuffer_layers[i].WindowY1 = self->height; + display.framebuffer_layers[i].PixelFormat = LTDC_PIXEL_FORMAT_RGB565; + display.framebuffer_layers[i].Alpha = 0; + display.framebuffer_layers[i].Alpha0 = 0; + display.framebuffer_layers[i].BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA; + display.framebuffer_layers[i].BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA; + display.framebuffer_layers[i].FBStartAdress = (uint32_t) self->framebuffers[i]; + display.framebuffer_layers[i].ImageWidth = self->width; + display.framebuffer_layers[i].ImageHeight = self->height; + display.framebuffer_layers[i].Backcolor.Blue = 0; + display.framebuffer_layers[i].Backcolor.Green = 0; + display.framebuffer_layers[i].Backcolor.Red = 0; + } + fb_alloc_mark_permanent(); + + display.hltdc.Instance = LTDC; + display.hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AH, + display.hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL, + display.hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL, + display.hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC, + + display.hltdc.Init.HorizontalSync = dm->hsync_len -1; + display.hltdc.Init.VerticalSync = dm->vsync_len - 1; + display.hltdc.Init.AccumulatedHBP = dm->hsync_len + dm->hback_porch -1; + display.hltdc.Init.AccumulatedVBP = dm->vsync_len + dm->vback_porch -1; + display.hltdc.Init.AccumulatedActiveW = dm->hsync_len + dm->hback_porch + self->width -1; + display.hltdc.Init.AccumulatedActiveH = dm->vsync_len + dm->vback_porch + self->height -1; + display.hltdc.Init.TotalWidth = dm->hsync_len + dm->hback_porch + self->width + dm->hfront_porch - 1; + display.hltdc.Init.TotalHeigh = dm->vsync_len + dm->vback_porch + self->height + dm->vfront_porch - 1; + + display.hltdc.Init.Backcolor.Blue = 0; + display.hltdc.Init.Backcolor.Green = 0; + display.hltdc.Init.Backcolor.Red = 0; + HAL_LTDC_Init(&display.hltdc); + + NVIC_SetPriority(LTDC_IRQn, IRQ_PRI_LTDC); + HAL_NVIC_EnableIRQ(LTDC_IRQn); + + // Start interrupt chain. + HAL_LTDC_ProgramLineEvent(&display.hltdc, 13); // AccumulatedVBP +} + +void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef *hltdc) { + py_display_obj_t *self = display.self; + + HAL_LTDC_ConfigLayer_NoReload(&display.hltdc, + &display.framebuffer_layers[self->framebuffer_tail], LTDC_LAYER_1); + HAL_LTDC_Reload(&display.hltdc, LTDC_RELOAD_VERTICAL_BLANKING); + + #if defined(OMV_DISPLAY_DISP_PIN) + if (self->display_on && (self->framebuffer_tail != self->framebuffer_head)) { + // Turn display on if there is a new command. + omv_gpio_write(OMV_DISPLAY_DISP_PIN, 1); + } + #endif + self->framebuffer_head = self->framebuffer_tail; + + // Continue chain... + HAL_LTDC_ProgramLineEvent(&display.hltdc, 13); // AccumulatedVBP +} + +static void display_write(py_display_obj_t *self, image_t *src_img, int dst_x_start, int dst_y_start, + float x_scale, float y_scale, rectangle_t *roi, int rgb_channel, int alpha, + const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint) { + image_t dst_img; + dst_img.w = self->width; + dst_img.h = self->height; + dst_img.pixfmt = PIXFORMAT_RGB565; + + int x0, x1, y0, y1; + bool black = !imlib_draw_image_rectangle(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, y_scale, + roi, alpha, alpha_palette, hint, &x0, &x1, &y0, &y1); + + // For triple buffering we are never drawing where tail or head (which may instantly update to + // to be equal to tail) is. + int tail = (self->framebuffer_tail + 1) % FRAMEBUFFER_COUNT; + if (tail == self->framebuffer_head) { + tail = (tail + 1) % FRAMEBUFFER_COUNT; + } + dst_img.data = (uint8_t *) self->framebuffers[tail]; + + // Set default values for the layer to display the whole framebuffer. + display.framebuffer_layers[tail].WindowX0 = black ? 0 : x0; + display.framebuffer_layers[tail].WindowX1 = black ? self->width : x1; + display.framebuffer_layers[tail].WindowY0 = black ? 0 : y0; + display.framebuffer_layers[tail].WindowY1 = black ? self->height : y1; + display.framebuffer_layers[tail].Alpha = black ? 0 : fast_roundf((alpha * 255) / 256.f); + display.framebuffer_layers[tail].FBStartAdress = + black ? ((uint32_t) dst_img.data) : ((uint32_t) (IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, y0) + x0)); + display.framebuffer_layers[tail].ImageWidth = black ? self->width : dst_img.w; + display.framebuffer_layers[tail].ImageHeight = black ? self->height : (y1 - y0); + + // Set alpha to 256 here as we will use the layer alpha to blend the image into the background color of black for free. + if (!black) { + imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start, + x_scale, y_scale, roi, rgb_channel, 256, color_palette, + alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL); + } + + #ifdef __DCACHE_PRESENT + // Flush data for DMA + if (!black) { + SCB_CleanDCache_by_Addr((uint32_t *) dst_img.data, image_size(&dst_img)); + } + #endif + + // Update tail which means a new image is ready. + self->framebuffer_tail = tail; +} + +static void display_clear(py_display_obj_t *self, bool off) { + #if defined(OMV_DISPLAY_DISP_PIN) + if (self->display_on && off) { + // turns the display off (may not be black) + omv_gpio_write(OMV_DISPLAY_DISP_PIN, 0); + return; + } + #endif + + // For triple buffering we are never drawing where tail or head (which may instantly update to + // to be equal to tail) is. + int tail = (self->framebuffer_tail + 1) % FRAMEBUFFER_COUNT; + if (tail == self->framebuffer_head) { + tail = (tail + 1) % FRAMEBUFFER_COUNT; + } + + // Set default values for the layer to display the whole framebuffer. + display.framebuffer_layers[tail].WindowX0 = 0; + display.framebuffer_layers[tail].WindowX1 = self->width; + display.framebuffer_layers[tail].WindowY0 = 0; + display.framebuffer_layers[tail].WindowY1 = self->height; + display.framebuffer_layers[tail].Alpha = 0; + display.framebuffer_layers[tail].FBStartAdress = (uint32_t) self->framebuffers[tail]; + display.framebuffer_layers[tail].ImageWidth = self->width; + display.framebuffer_layers[tail].ImageHeight = self->height; + + // Update tail which means a new image is ready. + self->framebuffer_tail = tail; +} + +#ifdef OMV_DISPLAY_BL_PIN +static void display_set_backlight(py_display_obj_t *self, uint32_t intensity) { + #ifdef OMV_DISPLAY_BL_TIM + if ((self->intensity < 255) && (255 <= intensity)) { + #else + if ((self->intensity < 1) && (1 <= intensity)) { + #endif + omv_gpio_config(OMV_DISPLAY_BL_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_DISPLAY_BL_PIN, 1); + } else if ((0 < self->intensity) && (intensity <= 0)) { + omv_gpio_write(OMV_DISPLAY_BL_PIN, 0); + omv_gpio_deinit(OMV_DISPLAY_BL_PIN); + } + + #ifdef OMV_DISPLAY_BL_TIM + int tclk = OMV_DISPLAY_BL_TIM_PCLK_FREQ() * 2; + int period = (tclk / OMV_DISPLAY_BL_TIM_FREQ) - 1; + + if (((self->intensity <= 0) || (255 <= self->intensity)) && (0 < intensity) && (intensity < 255)) { + omv_gpio_config(OMV_DISPLAY_BL_PIN, OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + + display.htim.Instance = OMV_DISPLAY_BL_TIM; + display.htim.Init.Prescaler = 0; + display.htim.Init.CounterMode = TIM_COUNTERMODE_UP; + display.htim.Init.Period = period; + display.htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; + display.htim.Init.RepetitionCounter = 0; + display.htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + + TIM_OC_InitTypeDef lcd_tim_oc_handle; + lcd_tim_oc_handle.Pulse = (period * intensity) / 255; + lcd_tim_oc_handle.OCMode = TIM_OCMODE_PWM1; + lcd_tim_oc_handle.OCPolarity = TIM_OCPOLARITY_HIGH; + lcd_tim_oc_handle.OCNPolarity = TIM_OCNPOLARITY_HIGH; + lcd_tim_oc_handle.OCFastMode = TIM_OCFAST_DISABLE; + lcd_tim_oc_handle.OCIdleState = TIM_OCIDLESTATE_RESET; + lcd_tim_oc_handle.OCNIdleState = TIM_OCNIDLESTATE_RESET; + + HAL_TIM_PWM_Init(&display.htim); + HAL_TIM_PWM_ConfigChannel(&display.htim, &lcd_tim_oc_handle, OMV_DISPLAY_BL_TIM_CHANNEL); + HAL_TIM_PWM_Start(&display.htim, OMV_DISPLAY_BL_TIM_CHANNEL); + } else if ((0 < self->intensity) && (self->intensity < 255) && ((intensity <= 0) || (255 <= intensity))) { + HAL_TIM_PWM_Stop(&display.htim, OMV_DISPLAY_BL_TIM_CHANNEL); + HAL_TIM_PWM_DeInit(&display.htim); + } else if ((0 < self->intensity) && (self->intensity < 255) && (0 < intensity) && (intensity < 255)) { + __HAL_TIM_SET_COMPARE(&display.htim, OMV_DISPLAY_BL_TIM_CHANNEL, (period * intensity) / 255); + } + #endif + + self->intensity = intensity; +} +#endif // OMV_DISPLAY_BL_PIN + +static void display_deinit(py_display_obj_t *self) { + HAL_LTDC_DeInit(&display.hltdc); + HAL_NVIC_DisableIRQ(LTDC_IRQn); + + __HAL_RCC_PLL3_DISABLE(); + uint32_t tickstart = mp_hal_ticks_ms(); + while (__HAL_RCC_GET_FLAG(RCC_FLAG_PLL3RDY)) { + if ((mp_hal_ticks_ms() - tickstart) > PLL_TIMEOUT_VALUE) { + break; + } + } + + fb_alloc_free_till_mark_past_mark_permanent(); + + #ifdef OMV_DISPLAY_BL_PIN + if (self->display_on) { + // back to default state + display_set_backlight(self, 0); + } + #endif // OMV_DISPLAY_BL_PIN +} + +mp_obj_t display_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_framesize, ARG_refresh, ARG_display_on, ARG_triple_buffer, ARG_portrait }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_framesize, MP_ARG_INT, {.u_int = DISPLAY_RESOLUTION_FWVGA } }, + { MP_QSTR_refresh, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 60 } }, + { MP_QSTR_display_on, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, + { MP_QSTR_triple_buffer, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, + { MP_QSTR_portrait, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} }, + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if ((args[ARG_framesize].u_int < 0) || (args[ARG_framesize].u_int >= DISPLAY_RESOLUTION_MAX)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Frame Size!")); + } + if ((args[ARG_refresh].u_int < 30) || (args[ARG_refresh].u_int > 120)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Refresh Rate!")); + } + + py_display_obj_t *self = (py_display_obj_t *) m_new_obj_with_finaliser(py_display_obj_t); + self->base.type = &py_rgb_display_type; + self->refresh = args[ARG_refresh].u_int; + self->display_on = args[ARG_display_on].u_bool; + self->bgr = false; + self->triple_buffer = args[ARG_triple_buffer].u_bool; + self->framebuffer_tail = 0; + self->framebuffer_head = 0; + self->framesize = args[ARG_framesize].u_int; + if (args[ARG_portrait].u_bool) { + self->width = display_modes[self->framesize].vactive; + self->height = display_modes[self->framesize].hactive; + } else { + self->width = display_modes[self->framesize].hactive; + self->height = display_modes[self->framesize].vactive; + } + + // Store state to access it from IRQ handlers or callbacks + display.self = self; + + // Configure PLL3 for the selected mode clock. + pll_config(self->framesize, self->refresh); + + // Init LTDC controller + ltdc_init(self); + + #ifdef OMV_DISPLAY_BL_PIN + if (self->display_on) { + display_set_backlight(self, 255); // to on state + } + #endif // OMV_DISPLAY_BL_PIN + + return MP_OBJ_FROM_PTR(self); +} + +STATIC const py_display_p_t py_display_p = { + .deinit = display_deinit, + .clear = display_clear, + .write = display_write, + #ifdef OMV_DISPLAY_BL_PIN + .set_backlight = display_set_backlight, + #endif +}; + +MP_DEFINE_CONST_OBJ_TYPE( + py_rgb_display_type, + MP_QSTR_RGBDisplay, + MP_TYPE_FLAG_NONE, + make_new, display_make_new, + protocol, &py_display_p, + locals_dict, &py_display_locals_dict + ); +#endif // MICROPY_PY_DISPLAY diff --git a/src/omv/ports/stm32/stm32fxxx_hal_msp.c b/src/omv/ports/stm32/stm32fxxx_hal_msp.c index 2b3b66705..d9e4b9cf4 100644 --- a/src/omv/ports/stm32/stm32fxxx_hal_msp.c +++ b/src/omv/ports/stm32/stm32fxxx_hal_msp.c @@ -289,9 +289,9 @@ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) { } #endif // (OMV_XCLK_SOURCE == OMV_XCLK_TIM) - #if defined(OMV_LCD_BL_TIM) - if (htim->Instance == OMV_LCD_BL_TIM) { - OMV_LCD_BL_TIM_CLK_ENABLE(); + #if defined(OMV_DISPLAY_BL_TIM) + if (htim->Instance == OMV_DISPLAY_BL_TIM) { + OMV_DISPLAY_BL_TIM_CLK_ENABLE(); } #endif @@ -303,11 +303,11 @@ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) { } void HAL_TIM_PWM_MspDeInit(TIM_HandleTypeDef *htim) { - #if defined(OMV_LCD_BL_TIM) - if (htim->Instance == OMV_LCD_BL_TIM) { - OMV_LCD_BL_TIM_FORCE_RESET(); - OMV_LCD_BL_TIM_RELEASE_RESET(); - OMV_LCD_BL_TIM_CLK_DISABLE(); + #if defined(OMV_DISPLAY_BL_TIM) + if (htim->Instance == OMV_DISPLAY_BL_TIM) { + OMV_DISPLAY_BL_TIM_FORCE_RESET(); + OMV_DISPLAY_BL_TIM_RELEASE_RESET(); + OMV_DISPLAY_BL_TIM_CLK_DISABLE(); } #endif @@ -601,109 +601,128 @@ void HAL_JPEG_MspDeInit(JPEG_HandleTypeDef *hjpeg) { } #endif -#if defined(OMV_LCD_CONTROLLER) && (!defined(OMV_DSI_CONTROLLER)) -static const omv_gpio_t ltdc_pins[] = { - OMV_LCD_R0_PIN, - OMV_LCD_R1_PIN, - OMV_LCD_R2_PIN, - OMV_LCD_R3_PIN, - OMV_LCD_R4_PIN, - OMV_LCD_R5_PIN, - OMV_LCD_R6_PIN, - OMV_LCD_R7_PIN, - OMV_LCD_G0_PIN, - OMV_LCD_G1_PIN, - OMV_LCD_G2_PIN, - OMV_LCD_G3_PIN, - OMV_LCD_G4_PIN, - OMV_LCD_G5_PIN, - OMV_LCD_G6_PIN, - OMV_LCD_G7_PIN, - OMV_LCD_B0_PIN, - OMV_LCD_B1_PIN, - OMV_LCD_B2_PIN, - OMV_LCD_B3_PIN, - OMV_LCD_B4_PIN, - OMV_LCD_B5_PIN, - OMV_LCD_B6_PIN, - OMV_LCD_B7_PIN, - OMV_LCD_CLK_PIN, - OMV_LCD_DE_PIN, - OMV_LCD_HSYNC_PIN, - OMV_LCD_VSYNC_PIN, -}; -#endif - -#if defined(OMV_LCD_CONTROLLER) || defined(OMV_DSI_CONTROLLER) +#if defined(OMV_RGB_DISPLAY_CONTROLLER) void HAL_LTDC_MspInit(LTDC_HandleTypeDef *hltdc) { - #if defined(OMV_DSI_CONTROLLER) - if (hltdc->Instance == OMV_LCD_CONTROLLER) { - OMV_LCD_CLK_ENABLE(); + #if defined(OMV_RGB_DISPLAY_R0_PIN) + const omv_gpio_t ltdc_pins[] = { + OMV_RGB_DISPLAY_R0_PIN, + OMV_RGB_DISPLAY_R1_PIN, + OMV_RGB_DISPLAY_R2_PIN, + OMV_RGB_DISPLAY_R3_PIN, + OMV_RGB_DISPLAY_R4_PIN, + OMV_RGB_DISPLAY_R5_PIN, + OMV_RGB_DISPLAY_R6_PIN, + OMV_RGB_DISPLAY_R7_PIN, + OMV_RGB_DISPLAY_G0_PIN, + OMV_RGB_DISPLAY_G1_PIN, + OMV_RGB_DISPLAY_G2_PIN, + OMV_RGB_DISPLAY_G3_PIN, + OMV_RGB_DISPLAY_G4_PIN, + OMV_RGB_DISPLAY_G5_PIN, + OMV_RGB_DISPLAY_G6_PIN, + OMV_RGB_DISPLAY_G7_PIN, + OMV_RGB_DISPLAY_B0_PIN, + OMV_RGB_DISPLAY_B1_PIN, + OMV_RGB_DISPLAY_B2_PIN, + OMV_RGB_DISPLAY_B3_PIN, + OMV_RGB_DISPLAY_B4_PIN, + OMV_RGB_DISPLAY_B5_PIN, + OMV_RGB_DISPLAY_B6_PIN, + OMV_RGB_DISPLAY_B7_PIN, + OMV_RGB_DISPLAY_CLK_PIN, + OMV_RGB_DISPLAY_DE_PIN, + OMV_RGB_DISPLAY_HSYNC_PIN, + OMV_RGB_DISPLAY_VSYNC_PIN, + }; + #endif + + OMV_RGB_DISPLAY_CLK_ENABLE(); + + #if defined(OMV_RGB_DISPLAY_R0_PIN) + for (int i = 0; i < OMV_ARRAY_SIZE(ltdc_pins); i++) { + omv_gpio_config(ltdc_pins[i], OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MAX, -1); } - #elif defined(OMV_LCD_CONTROLLER) - if (hltdc->Instance == OMV_LCD_CONTROLLER) { - OMV_LCD_CLK_ENABLE(); + #endif - for (int i = 0; i < OMV_ARRAY_SIZE(ltdc_pins); i++) { - omv_gpio_config(ltdc_pins[i], OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MAX, -1); - } + #if defined(OMV_RGB_DISPLAY_DISP_PIN) + omv_gpio_config(OMV_RGB_DISPLAY_DISP_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_RGB_DISPLAY_DISP_PIN, 0); + #endif - #if defined(OMV_LCD_DISP_PIN) - omv_gpio_config(OMV_LCD_DISP_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); - omv_gpio_write(OMV_LCD_DISP_PIN, 0); - #endif - - #if defined(OMV_LCD_BL_PIN) - omv_gpio_config(OMV_LCD_BL_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); - omv_gpio_write(OMV_LCD_BL_PIN, 0); - #endif - } + #if defined(OMV_RGB_DISPLAY_BL_PIN) + omv_gpio_config(OMV_RGB_DISPLAY_BL_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); + omv_gpio_write(OMV_RGB_DISPLAY_BL_PIN, 0); #endif } void HAL_LTDC_MspDeInit(LTDC_HandleTypeDef *hltdc) { - #if defined(OMV_DSI_CONTROLLER) - if (hltdc->Instance == OMV_LCD_CONTROLLER) { - OMV_LCD_FORCE_RESET(); - OMV_LCD_RELEASE_RESET(); - OMV_LCD_CLK_DISABLE(); + #if defined(OMV_RGB_DISPLAY_R0_PIN) + const omv_gpio_t ltdc_pins[] = { + OMV_RGB_DISPLAY_R0_PIN, + OMV_RGB_DISPLAY_R1_PIN, + OMV_RGB_DISPLAY_R2_PIN, + OMV_RGB_DISPLAY_R3_PIN, + OMV_RGB_DISPLAY_R4_PIN, + OMV_RGB_DISPLAY_R5_PIN, + OMV_RGB_DISPLAY_R6_PIN, + OMV_RGB_DISPLAY_R7_PIN, + OMV_RGB_DISPLAY_G0_PIN, + OMV_RGB_DISPLAY_G1_PIN, + OMV_RGB_DISPLAY_G2_PIN, + OMV_RGB_DISPLAY_G3_PIN, + OMV_RGB_DISPLAY_G4_PIN, + OMV_RGB_DISPLAY_G5_PIN, + OMV_RGB_DISPLAY_G6_PIN, + OMV_RGB_DISPLAY_G7_PIN, + OMV_RGB_DISPLAY_B0_PIN, + OMV_RGB_DISPLAY_B1_PIN, + OMV_RGB_DISPLAY_B2_PIN, + OMV_RGB_DISPLAY_B3_PIN, + OMV_RGB_DISPLAY_B4_PIN, + OMV_RGB_DISPLAY_B5_PIN, + OMV_RGB_DISPLAY_B6_PIN, + OMV_RGB_DISPLAY_B7_PIN, + OMV_RGB_DISPLAY_CLK_PIN, + OMV_RGB_DISPLAY_DE_PIN, + OMV_RGB_DISPLAY_HSYNC_PIN, + OMV_RGB_DISPLAY_VSYNC_PIN, + }; + #endif + + OMV_RGB_DISPLAY_FORCE_RESET(); + OMV_RGB_DISPLAY_RELEASE_RESET(); + OMV_RGB_DISPLAY_CLK_DISABLE(); + + #if defined(OMV_RGB_DISPLAY_R0_PIN) + for (int i = 0; i < OMV_ARRAY_SIZE(ltdc_pins); i++) { + omv_gpio_deinit(ltdc_pins[i]); } - #elif defined(OMV_LCD_CONTROLLER) - if (hltdc->Instance == OMV_LCD_CONTROLLER) { - OMV_LCD_FORCE_RESET(); - OMV_LCD_RELEASE_RESET(); - OMV_LCD_CLK_DISABLE(); + #endif - for (int i = 0; i < OMV_ARRAY_SIZE(ltdc_pins); i++) { - omv_gpio_deinit(ltdc_pins[i]); - } + #if defined(OMV_RGB_DISPLAY_DISP_PIN) + omv_gpio_deinit(OMV_RGB_DISPLAY_DISP_PIN); + #endif - #if defined(OMV_LCD_DISP_PIN) - omv_gpio_deinit(OMV_LCD_DISP_PIN); - #endif - - #if defined(OMV_LCD_BL_PIN) - omv_gpio_deinit(OMV_LCD_BL_PIN); - #endif - } + #if defined(OMV_RGB_DISPLAY_BL_PIN) + omv_gpio_deinit(OMV_RGB_DISPLAY_BL_PIN); #endif } #endif void HAL_DAC_MspInit(DAC_HandleTypeDef *hdac) { - #if defined(OMV_SPI_LCD_BL_DAC) - if (hdac->Instance == OMV_SPI_LCD_BL_DAC) { - OMV_SPI_LCD_BL_DAC_CLK_ENABLE(); + #if defined(OMV_DISPLAY_BL_DAC) + if (hdac->Instance == OMV_DISPLAY_BL_DAC) { + OMV_DISPLAY_BL_DAC_CLK_ENABLE(); } #endif } void HAL_DAC_MspDeInit(DAC_HandleTypeDef *hdac) { - #if defined(OMV_SPI_LCD_BL_DAC) - if (hdac->Instance == OMV_SPI_LCD_BL_DAC) { - OMV_SPI_LCD_BL_DAC_FORCE_RESET(); - OMV_SPI_LCD_BL_DAC_RELEASE_RESET(); - OMV_SPI_LCD_BL_DAC_CLK_DISABLE(); + #if defined(OMV_DISPLAY_BL_DAC) + if (hdac->Instance == OMV_DISPLAY_BL_DAC) { + OMV_DISPLAY_BL_DAC_FORCE_RESET(); + OMV_DISPLAY_BL_DAC_RELEASE_RESET(); + OMV_DISPLAY_BL_DAC_CLK_DISABLE(); } #endif }