openmv/src/omv/modules/py_lcd.c

1846 lines
72 KiB
C

/*
* 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.
*/
#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(&ltdc_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(&ltdc_handle.Init, &resolution_cfg[frame_size], sizeof(LTDC_InitTypeDef));
HAL_LTDC_Init(&ltdc_handle);
NVIC_SetPriority(LTDC_IRQn, IRQ_PRI_LTDC);
HAL_NVIC_EnableIRQ(LTDC_IRQn);
fb_alloc_mark_permanent();
// Start interrupt chain.
HAL_LTDC_ProgramLineEvent(&ltdc_handle, 13); // AccumulatedVBP
}
void LTDC_IRQHandler() {
IRQ_ENTER(LTDC_IRQn);
HAL_LTDC_IRQHandler(&ltdc_handle);
IRQ_EXIT(LTDC_IRQn);
}
void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef *hltdc) {
HAL_LTDC_ConfigLayer_NoReload(&ltdc_handle, &ltdc_framebuffer_layers[framebuffer_tail], LTDC_LAYER_1);
HAL_LTDC_Reload(&ltdc_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(&ltdc_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 = &reg
}), 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