micropython: Refactor all display related modules.

This commit is contained in:
iabdalkader 2023-09-24 16:31:56 +02:00
parent 7467731a38
commit 227824aaca
11 changed files with 1420 additions and 1980 deletions

View File

@ -173,9 +173,9 @@ MPY_CFLAGS += -DMICROPY_PY_MICRO_SPEECH=1
MICROPY_ARGS += MICROPY_PY_MICRO_SPEECH=1 MICROPY_ARGS += MICROPY_PY_MICRO_SPEECH=1
endif endif
ifeq ($(MICROPY_PY_LCD), 1) ifeq ($(MICROPY_PY_DISPLAY), 1)
MPY_CFLAGS += -DMICROPY_PY_LCD=1 MPY_CFLAGS += -DMICROPY_PY_DISPLAY=1
MICROPY_ARGS += MICROPY_PY_LCD=1 MICROPY_ARGS += MICROPY_PY_DISPLAY=1
endif endif
ifeq ($(MICROPY_PY_TV), 1) ifeq ($(MICROPY_PY_TV), 1)

View File

@ -0,0 +1,246 @@
/*
* This file is part of the OpenMV project.
*
* Copyright (c) 2013-2023 Ibrahim Abdelkader <iabdalkader@openmv.io>
* Copyright (c) 2013-2023 Kwabena W. Agyeman <kwagyeman@openmv.io>
*
* 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

View File

@ -0,0 +1,78 @@
/*
* This file is part of the OpenMV project.
*
* Copyright (c) 2013-2023 Ibrahim Abdelkader <iabdalkader@openmv.io>
* Copyright (c) 2013-2023 Kwabena W. Agyeman <kwagyeman@openmv.io>
*
* 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__

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
/*
* This file is part of the OpenMV project.
*
* Copyright (c) 2013-2021 Ibrahim Abdelkader <iabdalkader@openmv.io>
* Copyright (c) 2013-2021 Kwabena W. Agyeman <kwagyeman@openmv.io>
*
* 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__

View File

@ -0,0 +1,454 @@
/*
* This file is part of the OpenMV project.
*
* Copyright (c) 2013-2021 Ibrahim Abdelkader <iabdalkader@openmv.io>
* Copyright (c) 2013-2021 Kwabena W. Agyeman <kwagyeman@openmv.io>
*
* 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

View File

@ -34,7 +34,7 @@
#error "TV_HEIGHT not even" #error "TV_HEIGHT not even"
#endif #endif
#ifdef OMV_SPI_LCD_SPI_BUS #ifdef OMV_SPI_DISPLAY_CONTROLLER
///////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////
// http://www.vsdsp-forum.com/phpbb/viewtopic.php?f=14&t=1801 // 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 .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); omv_spi_transfer_start(&spi_bus, &spi_xfer);
if (end) { 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; static bool tv_triple_buffer = false;
#ifdef OMV_SPI_LCD_SPI_BUS #ifdef OMV_SPI_DISPLAY_CONTROLLER
static volatile enum { static volatile enum {
SPI_TX_CB_IDLE, SPI_TX_CB_IDLE,
SPI_TX_CB_MEMORY_WRITE_CMD, SPI_TX_CB_MEMORY_WRITE_CMD,
@ -415,14 +415,14 @@ static void spi_config_deinit() {
static void spi_config_init(bool triple_buffer) { static void spi_config_init(bool triple_buffer) {
omv_spi_config_t spi_config; 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.baudrate = TV_BAUDRATE;
spi_config.nss_enable = false; spi_config.nss_enable = false;
spi_config.dma_flags = triple_buffer ? OMV_SPI_DMA_NORMAL : 0; spi_config.dma_flags = triple_buffer ? OMV_SPI_DMA_NORMAL : 0;
omv_spi_init(&spi_bus, &spi_config); 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(); SpiRamVideoInit();
@ -458,12 +458,12 @@ static void spi_tv_callback(omv_spi_t *spi, void *userdata, void *buf) {
switch (spi_tx_cb_state) { switch (spi_tx_cb_state) {
case SPI_TX_CB_MEMORY_WRITE_CMD: { 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 = SPI_TX_CB_MEMORY_WRITE;
spi_tx_cb_state_memory_write_addr = (uint8_t *) framebuffers[framebuffer_head]; spi_tx_cb_state_memory_write_addr = (uint8_t *) framebuffers[framebuffer_head];
spi_tx_cb_state_memory_write_count = PICLINE_LENGTH_BYTES * TV_HEIGHT; spi_tx_cb_state_memory_write_count = PICLINE_LENGTH_BYTES * TV_HEIGHT;
framebuffer_tail = framebuffer_head; 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 // When starting the interrupt chain the first transfer is not executed
// in interrupt context. So, disable interrupts for the first transfer so // in interrupt context. So, disable interrupts for the first transfer so
// that it completes first and unlocks the SPI bus before allowing the interrupt // 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(); fb_free();
} else { } else {
// For triple buffering we are never drawing where head or tail (which may instantly update to // 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() { STATIC mp_obj_t py_tv_deinit() {
switch (tv_type) { switch (tv_type) {
#ifdef OMV_SPI_LCD_SPI_BUS #ifdef OMV_SPI_DISPLAY_CONTROLLER
case TV_SHIELD: { case TV_SHIELD: {
spi_config_deinit(); spi_config_deinit();
break; 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); MP_OBJ_NEW_QSTR(MP_QSTR_type), TV_SHIELD);
switch (type) { switch (type) {
#ifdef OMV_SPI_LCD_SPI_BUS #ifdef OMV_SPI_DISPLAY_CONTROLLER
case TV_SHIELD: { case TV_SHIELD: {
bool triple_buffer_def = false; bool triple_buffer_def = false;
#ifdef OMV_SPI_LCD_DEF_TRIPLE_BUF #ifdef OMV_SPI_DISPLAY_TRIPLE_BUFFER
triple_buffer_def = OMV_SPI_LCD_DEF_TRIPLE_BUF; triple_buffer_def = OMV_SPI_DISPLAY_TRIPLE_BUFFER;
#endif #endif
bool triple_buffer = py_helper_keyword_int(n_args, args, 1, kw_args, bool triple_buffer = py_helper_keyword_int(n_args, args, 1, kw_args,
MP_OBJ_NEW_QSTR(MP_QSTR_triple_buffer), triple_buffer_def); 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; return mp_const_none;
} }
#ifdef OMV_SPI_LCD_SPI_BUS #ifdef OMV_SPI_DISPLAY_CONTROLLER
if (tv_triple_buffer) { if (tv_triple_buffer) {
omv_spi_transfer_abort(&spi_bus); omv_spi_transfer_abort(&spi_bus);
spi_tx_cb_state = SPI_TX_CB_IDLE; 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) { 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)); SpiRamWriteByteRegister(WRITE_GPIO, 0x70 | (channel - 1));
} else { } else {
#ifdef OMV_SPI_LCD_RX_CLK_DIV #ifdef OMV_SPI_DISPLAY_RX_CLK_DIV
omv_spi_set_baudrate(&spi_bus, TV_BAUDRATE / OMV_SPI_LCD_RX_CLK_DIV); omv_spi_set_baudrate(&spi_bus, TV_BAUDRATE / OMV_SPI_DISPLAY_RX_CLK_DIV);
#endif #endif
int channel = SpiRamReadByteRegister(READ_GPIO); 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); omv_spi_set_baudrate(&spi_bus, TV_BAUDRATE);
#endif #endif
return mp_obj_new_int((channel & 0x7) + 1); 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) { switch (tv_type) {
#ifdef OMV_SPI_LCD_SPI_BUS #ifdef OMV_SPI_DISPLAY_CONTROLLER
case TV_SHIELD: { case TV_SHIELD: {
fb_alloc_mark(); fb_alloc_mark();
spi_tv_display(arg_img, arg_x_off, arg_y_off, arg_x_scale, arg_y_scale, &arg_roi, 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() { STATIC mp_obj_t py_tv_clear() {
switch (tv_type) { switch (tv_type) {
#ifdef OMV_SPI_LCD_SPI_BUS #ifdef OMV_SPI_DISPLAY_CONTROLLER
case TV_SHIELD: { case TV_SHIELD: {
fb_alloc_mark(); fb_alloc_mark();
spi_tv_display(NULL, 0, 0, 1.f, 1.f, NULL, spi_tv_display(NULL, 0, 0, 1.f, 1.f, NULL,

View File

@ -28,7 +28,6 @@
#include "systick.h" #include "systick.h"
#include "modmimxrt.h" #include "modmimxrt.h"
#include "py_lcd.h"
#include "py_fir.h" #include "py_fir.h"
#include "py_tv.h" #include "py_tv.h"
@ -115,9 +114,6 @@ soft_reset:
mp_init(); mp_init();
// Initialise low-level sub-systems. // Initialise low-level sub-systems.
#if MICROPY_PY_LCD
py_lcd_init0();
#endif
py_fir_init0(); py_fir_init0();
#if MICROPY_PY_TV #if MICROPY_PY_TV
py_tv_init0(); py_tv_init0();

View File

@ -67,7 +67,6 @@
#include "usbd_cdc_interface.h" #include "usbd_cdc_interface.h"
#include "py_image.h" #include "py_image.h"
#include "py_lcd.h"
#include "py_fir.h" #include "py_fir.h"
#include "py_tv.h" #include "py_tv.h"
#include "py_buzzer.h" #include "py_buzzer.h"
@ -359,9 +358,6 @@ soft_reset:
// Initialise low-level sub-systems. Here we need to do the very basic // 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. // things like zeroing out memory and resetting any of the sub-systems.
#if MICROPY_PY_LCD
py_lcd_init0();
#endif
py_fir_init0(); py_fir_init0();
#if MICROPY_PY_TV #if MICROPY_PY_TV
py_tv_init0(); py_tv_init0();

View File

@ -0,0 +1,510 @@
/*
* This file is part of the OpenMV project.
*
* Copyright (c) 2013-2021 Ibrahim Abdelkader <iabdalkader@openmv.io>
* Copyright (c) 2013-2021 Kwabena W. Agyeman <kwagyeman@openmv.io>
*
* 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

View File

@ -289,9 +289,9 @@ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) {
} }
#endif // (OMV_XCLK_SOURCE == OMV_XCLK_TIM) #endif // (OMV_XCLK_SOURCE == OMV_XCLK_TIM)
#if defined(OMV_LCD_BL_TIM) #if defined(OMV_DISPLAY_BL_TIM)
if (htim->Instance == OMV_LCD_BL_TIM) { if (htim->Instance == OMV_DISPLAY_BL_TIM) {
OMV_LCD_BL_TIM_CLK_ENABLE(); OMV_DISPLAY_BL_TIM_CLK_ENABLE();
} }
#endif #endif
@ -303,11 +303,11 @@ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) {
} }
void HAL_TIM_PWM_MspDeInit(TIM_HandleTypeDef *htim) { void HAL_TIM_PWM_MspDeInit(TIM_HandleTypeDef *htim) {
#if defined(OMV_LCD_BL_TIM) #if defined(OMV_DISPLAY_BL_TIM)
if (htim->Instance == OMV_LCD_BL_TIM) { if (htim->Instance == OMV_DISPLAY_BL_TIM) {
OMV_LCD_BL_TIM_FORCE_RESET(); OMV_DISPLAY_BL_TIM_FORCE_RESET();
OMV_LCD_BL_TIM_RELEASE_RESET(); OMV_DISPLAY_BL_TIM_RELEASE_RESET();
OMV_LCD_BL_TIM_CLK_DISABLE(); OMV_DISPLAY_BL_TIM_CLK_DISABLE();
} }
#endif #endif
@ -601,109 +601,128 @@ void HAL_JPEG_MspDeInit(JPEG_HandleTypeDef *hjpeg) {
} }
#endif #endif
#if defined(OMV_LCD_CONTROLLER) && (!defined(OMV_DSI_CONTROLLER)) #if defined(OMV_RGB_DISPLAY_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)
void HAL_LTDC_MspInit(LTDC_HandleTypeDef *hltdc) { void HAL_LTDC_MspInit(LTDC_HandleTypeDef *hltdc) {
#if defined(OMV_DSI_CONTROLLER) #if defined(OMV_RGB_DISPLAY_R0_PIN)
if (hltdc->Instance == OMV_LCD_CONTROLLER) { const omv_gpio_t ltdc_pins[] = {
OMV_LCD_CLK_ENABLE(); 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) #endif
if (hltdc->Instance == OMV_LCD_CONTROLLER) {
OMV_LCD_CLK_ENABLE();
for (int i = 0; i < OMV_ARRAY_SIZE(ltdc_pins); i++) { #if defined(OMV_RGB_DISPLAY_DISP_PIN)
omv_gpio_config(ltdc_pins[i], OMV_GPIO_MODE_ALT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_MAX, -1); 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) #if defined(OMV_RGB_DISPLAY_BL_PIN)
omv_gpio_config(OMV_LCD_DISP_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1); 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_LCD_DISP_PIN, 0); omv_gpio_write(OMV_RGB_DISPLAY_BL_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
}
#endif #endif
} }
void HAL_LTDC_MspDeInit(LTDC_HandleTypeDef *hltdc) { void HAL_LTDC_MspDeInit(LTDC_HandleTypeDef *hltdc) {
#if defined(OMV_DSI_CONTROLLER) #if defined(OMV_RGB_DISPLAY_R0_PIN)
if (hltdc->Instance == OMV_LCD_CONTROLLER) { const omv_gpio_t ltdc_pins[] = {
OMV_LCD_FORCE_RESET(); OMV_RGB_DISPLAY_R0_PIN,
OMV_LCD_RELEASE_RESET(); OMV_RGB_DISPLAY_R1_PIN,
OMV_LCD_CLK_DISABLE(); 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) #endif
if (hltdc->Instance == OMV_LCD_CONTROLLER) {
OMV_LCD_FORCE_RESET();
OMV_LCD_RELEASE_RESET();
OMV_LCD_CLK_DISABLE();
for (int i = 0; i < OMV_ARRAY_SIZE(ltdc_pins); i++) { #if defined(OMV_RGB_DISPLAY_DISP_PIN)
omv_gpio_deinit(ltdc_pins[i]); omv_gpio_deinit(OMV_RGB_DISPLAY_DISP_PIN);
} #endif
#if defined(OMV_LCD_DISP_PIN) #if defined(OMV_RGB_DISPLAY_BL_PIN)
omv_gpio_deinit(OMV_LCD_DISP_PIN); omv_gpio_deinit(OMV_RGB_DISPLAY_BL_PIN);
#endif
#if defined(OMV_LCD_BL_PIN)
omv_gpio_deinit(OMV_LCD_BL_PIN);
#endif
}
#endif #endif
} }
#endif #endif
void HAL_DAC_MspInit(DAC_HandleTypeDef *hdac) { void HAL_DAC_MspInit(DAC_HandleTypeDef *hdac) {
#if defined(OMV_SPI_LCD_BL_DAC) #if defined(OMV_DISPLAY_BL_DAC)
if (hdac->Instance == OMV_SPI_LCD_BL_DAC) { if (hdac->Instance == OMV_DISPLAY_BL_DAC) {
OMV_SPI_LCD_BL_DAC_CLK_ENABLE(); OMV_DISPLAY_BL_DAC_CLK_ENABLE();
} }
#endif #endif
} }
void HAL_DAC_MspDeInit(DAC_HandleTypeDef *hdac) { void HAL_DAC_MspDeInit(DAC_HandleTypeDef *hdac) {
#if defined(OMV_SPI_LCD_BL_DAC) #if defined(OMV_DISPLAY_BL_DAC)
if (hdac->Instance == OMV_SPI_LCD_BL_DAC) { if (hdac->Instance == OMV_DISPLAY_BL_DAC) {
OMV_SPI_LCD_BL_DAC_FORCE_RESET(); OMV_DISPLAY_BL_DAC_FORCE_RESET();
OMV_SPI_LCD_BL_DAC_RELEASE_RESET(); OMV_DISPLAY_BL_DAC_RELEASE_RESET();
OMV_SPI_LCD_BL_DAC_CLK_DISABLE(); OMV_DISPLAY_BL_DAC_CLK_DISABLE();
} }
#endif #endif
} }