From b244a997895093e8fdf84fe6fbe28809a44ee634 Mon Sep 17 00:00:00 2001 From: Nunzio Raciti Date: Thu, 26 Mar 2026 09:14:44 +0100 Subject: [PATCH 1/4] submodule: update lv_binding_micropython for LTDC driver --- driver/stm32/DEV190806042/bsp_ltdc.c | 103 ++ driver/stm32/FK743M5-XIH6/bsp_ltdc.c | 105 ++ driver/stm32/STM32H7_CORE/bsp_ltdc.c | 132 ++ driver/stm32/st_ltdc/board_config.py | 2 + driver/stm32/st_ltdc/boards.py | 58 + driver/stm32/st_ltdc/display_conf.py | 26 + driver/stm32/st_ltdc/display_driver.py | 4 + driver/stm32/st_ltdc/lv_conf.h | 1418 +++++++++++++++++++ driver/stm32/st_ltdc/manifest.py | 7 + driver/stm32/st_ltdc/modules/micropython.mk | 14 + driver/stm32/st_ltdc/modules/st_ltdc.c | 629 ++++++++ driver/stm32/st_ltdc/modules/touch_i2c.c | 291 ++++ driver/stm32/st_ltdc/panels.py | 22 + driver/stm32/st_ltdc/st_ltdc_utils.py | 59 + examples/advanced_demo.py | 63 + 15 files changed, 2933 insertions(+) create mode 100644 driver/stm32/DEV190806042/bsp_ltdc.c create mode 100644 driver/stm32/FK743M5-XIH6/bsp_ltdc.c create mode 100644 driver/stm32/STM32H7_CORE/bsp_ltdc.c create mode 100644 driver/stm32/st_ltdc/board_config.py create mode 100644 driver/stm32/st_ltdc/boards.py create mode 100644 driver/stm32/st_ltdc/display_conf.py create mode 100644 driver/stm32/st_ltdc/display_driver.py create mode 100644 driver/stm32/st_ltdc/lv_conf.h create mode 100644 driver/stm32/st_ltdc/manifest.py create mode 100644 driver/stm32/st_ltdc/modules/micropython.mk create mode 100644 driver/stm32/st_ltdc/modules/st_ltdc.c create mode 100644 driver/stm32/st_ltdc/modules/touch_i2c.c create mode 100644 driver/stm32/st_ltdc/panels.py create mode 100644 driver/stm32/st_ltdc/st_ltdc_utils.py diff --git a/driver/stm32/DEV190806042/bsp_ltdc.c b/driver/stm32/DEV190806042/bsp_ltdc.c new file mode 100644 index 0000000000..cd38e581a5 --- /dev/null +++ b/driver/stm32/DEV190806042/bsp_ltdc.c @@ -0,0 +1,103 @@ +// LTDC display BSP driver for DEV190806042 board. + +#include +#include +#include +#include +#include "boardctrl.h" +#include + +// HAL MSP initialization for the LTDC peripheral. +// Configures PLL3 for the pixel clock, enables GPIO clocks and maps +// the LTDC alternate-function pins. +void HAL_LTDC_MspInit(LTDC_HandleTypeDef *ltdc_handle) { + GPIO_InitTypeDef gpio = {0}; + + RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; + PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; + PeriphClkInitStruct.PLL3.PLL3M = 5; + PeriphClkInitStruct.PLL3.PLL3N = 40; + PeriphClkInitStruct.PLL3.PLL3P = 2; + PeriphClkInitStruct.PLL3.PLL3Q = 4; + PeriphClkInitStruct.PLL3.PLL3R = 4; + PeriphClkInitStruct.PLL3.PLL3RGE = RCC_PLL3VCIRANGE_2; + PeriphClkInitStruct.PLL3.PLL3VCOSEL = RCC_PLL3VCOWIDE; + PeriphClkInitStruct.PLL3.PLL3FRACN = 0; + + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { + boardctrl_fatal_error("HAL_LTDC_MspInit()"); + } + + if (ltdc_handle->Instance == LTDC) { + // LTDC and GPIO clock enable + __HAL_RCC_GPIOI_CLK_ENABLE(); + __HAL_RCC_GPIOJ_CLK_ENABLE(); + __HAL_RCC_GPIOK_CLK_ENABLE(); + __HAL_RCC_LTDC_CLK_ENABLE(); + // Common settings for all LTDC data/sync/clk pins + gpio.Mode = GPIO_MODE_AF_PP; + gpio.Pull = GPIO_NOPULL; + gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + gpio.Alternate = GPIO_AF14_LTDC; + + // Port I: CLK, HSYNC, VSYNC, R0 + // PI12 = LTDC_HSYNC + // PI13 = LTDC_VSYNC + // PI14 = LTDC_CLK + // PI15 = LTDC_R0 + gpio.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; + HAL_GPIO_Init(GPIOI, &gpio); + + // Port J: R1-R7, G0-G4, B0-B3 (PJ0-PJ15, all 16 pins) + // PJ0 = LTDC_R1 PJ1 = LTDC_R2 PJ2 = LTDC_R3 + // PJ3 = LTDC_R4 PJ4 = LTDC_R5 PJ5 = LTDC_R6 + // PJ6 = LTDC_R7 + // PJ7 = LTDC_G0 PJ8 = LTDC_G1 PJ9 = LTDC_G2 + // PJ10 = LTDC_G3 PJ11 = LTDC_G4 + // PJ12 = LTDC_B0 PJ13 = LTDC_B1 PJ14 = LTDC_B2 + // PJ15 = LTDC_B3 + gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | + GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | + GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | + GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; + HAL_GPIO_Init(GPIOJ, &gpio); + + // Port K: G5-G7, B4-B7, DE (PK0-PK7) + // PK0 = LTDC_G5 PK1 = LTDC_G6 PK2 = LTDC_G7 + // PK3 = LTDC_B4 PK4 = LTDC_B5 PK5 = LTDC_B6 + // PK6 = LTDC_B7 PK7 = LTDC_DE + gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | + GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; + HAL_GPIO_Init(GPIOK, &gpio); + // LTDC interrupt + HAL_NVIC_SetPriority(LTDC_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(LTDC_IRQn); + } +} + +// HAL MSP de-initialization for the LTDC peripheral. +// Resets the LTDC block, disables its clock and de-initializes all +// associated GPIO pins. +void HAL_LTDC_MspDeInit(LTDC_HandleTypeDef *ltdc_handle) { + if (ltdc_handle->Instance == LTDC) { + __HAL_RCC_LTDC_FORCE_RESET(); + __HAL_RCC_LTDC_RELEASE_RESET(); + HAL_NVIC_DisableIRQ(LTDC_IRQn); + __HAL_RCC_LTDC_CLK_DISABLE(); + // De-init Port I LTDC pins + HAL_GPIO_DeInit(GPIOI, GPIO_PIN_12 | GPIO_PIN_13 | + GPIO_PIN_14 | GPIO_PIN_15); + + // De-init Port J - all 16 LTDC pins + HAL_GPIO_DeInit(GPIOJ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | + GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | + GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | + GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); + + // De-init Port K LTDC pins + HAL_GPIO_DeInit(GPIOK, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | + GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); + } +} + + diff --git a/driver/stm32/FK743M5-XIH6/bsp_ltdc.c b/driver/stm32/FK743M5-XIH6/bsp_ltdc.c new file mode 100644 index 0000000000..a45cfc8292 --- /dev/null +++ b/driver/stm32/FK743M5-XIH6/bsp_ltdc.c @@ -0,0 +1,105 @@ +// LTDC display BSP driver for FK743M5-XIH6 board. + +#include +#include +#include +#include +#include "boardctrl.h" +#include + +// HAL MSP initialization for the LTDC peripheral. +// Configures PLL3 for the pixel clock, enables GPIO clocks and maps +// the LTDC alternate-function pins. +void HAL_LTDC_MspInit(LTDC_HandleTypeDef *ltdc_handle) { + GPIO_InitTypeDef gpio = {0}; + + RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; + PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; + PeriphClkInitStruct.PLL3.PLL3M = 5; + PeriphClkInitStruct.PLL3.PLL3N = 40; + PeriphClkInitStruct.PLL3.PLL3P = 2; + PeriphClkInitStruct.PLL3.PLL3Q = 4; + PeriphClkInitStruct.PLL3.PLL3R = 4; + PeriphClkInitStruct.PLL3.PLL3RGE = RCC_PLL3VCIRANGE_2; + PeriphClkInitStruct.PLL3.PLL3VCOSEL = RCC_PLL3VCOWIDE; + PeriphClkInitStruct.PLL3.PLL3FRACN = 0; + + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { + boardctrl_fatal_error("HAL_LTDC_MspInit()"); + } + + if (ltdc_handle->Instance == LTDC) { + // LTDC and GPIO clock enable + __HAL_RCC_GPIOG_CLK_ENABLE(); + __HAL_RCC_GPIOH_CLK_ENABLE(); + __HAL_RCC_GPIOI_CLK_ENABLE(); + __HAL_RCC_GPIOJ_CLK_ENABLE(); + __HAL_RCC_GPIOK_CLK_ENABLE(); + __HAL_RCC_LTDC_CLK_ENABLE(); + // Common settings for all LTDC data/sync/clk pins + gpio.Mode = GPIO_MODE_AF_PP; + gpio.Pull = GPIO_NOPULL; + gpio.Speed = GPIO_SPEED_FREQ_HIGH; + + // GPIOG: G3 = LCD_G3 + gpio.Pin = GPIO_PIN_10; // PG10 = LCD_G3 + gpio.Alternate = GPIO_AF9_LTDC; + HAL_GPIO_Init(GPIOG, &gpio); + + // GPIOH: PH4=G5 + gpio.Pin = GPIO_PIN_4; + gpio.Alternate = GPIO_AF9_LTDC; + HAL_GPIO_Init(GPIOH, &gpio); + + // GPIOH: PH15=G4 + gpio.Pin = GPIO_PIN_15; + gpio.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOH, &gpio); + + // GPIOI: PI12=HSYNC PI13=VSYNC PI14=CLK PI15=R0 + gpio.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; + gpio.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOI, &gpio); + + // GPIOJ: PJ0..PJ9 and PJ12..PJ15 + // R1=PJ0 R2=PJ1 R3=PJ2 R4=PJ3 R5=PJ4 R6=PJ5 R7=PJ6 + // G0=PJ7 G1=PJ8 G2=PJ9 + // B0=PJ12 B1=PJ13 B2=PJ14 B3=PJ15 + gpio.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | + GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | + GPIO_PIN_8 | GPIO_PIN_9 | + GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; + HAL_GPIO_Init(GPIOJ, &gpio); + + // GPIOK: PK1=G6 PK2=G7 PK3=B4 PK4=B5 PK5=B6 PK6=B7 PK7=DE + gpio.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | + GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; + HAL_GPIO_Init(GPIOK, &gpio); + // LTDC interrupt + HAL_NVIC_SetPriority(LTDC_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(LTDC_IRQn); + } +} + +// HAL MSP de-initialization for the LTDC peripheral. +// Resets the LTDC block, disables its clock and de-initializes all +// associated GPIO pins. +void HAL_LTDC_MspDeInit(LTDC_HandleTypeDef *ltdc_handle) { + if (ltdc_handle->Instance == LTDC) { + __HAL_RCC_LTDC_FORCE_RESET(); + __HAL_RCC_LTDC_RELEASE_RESET(); + HAL_NVIC_DisableIRQ(LTDC_IRQn); + __HAL_RCC_LTDC_CLK_DISABLE(); + HAL_GPIO_DeInit(GPIOG, GPIO_PIN_10); + HAL_GPIO_DeInit(GPIOH, GPIO_PIN_4 | GPIO_PIN_15); + HAL_GPIO_DeInit(GPIOI, GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); + HAL_GPIO_DeInit(GPIOJ, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | + GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | + GPIO_PIN_8 | GPIO_PIN_9 | + GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); + HAL_GPIO_DeInit(GPIOK, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | + GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); + } +} + + diff --git a/driver/stm32/STM32H7_CORE/bsp_ltdc.c b/driver/stm32/STM32H7_CORE/bsp_ltdc.c new file mode 100644 index 0000000000..8a0b8df484 --- /dev/null +++ b/driver/stm32/STM32H7_CORE/bsp_ltdc.c @@ -0,0 +1,132 @@ +// LTDC display BSP driver for STM32H7_CORE boards. + +#include +#include +#include +#include +#include "boardctrl.h" +#include + +// HAL MSP initialization for the LTDC peripheral. +// Configures PLL3 for the pixel clock, enables GPIO clocks and maps +// the LTDC alternate-function pins. +void HAL_LTDC_MspInit(LTDC_HandleTypeDef *ltdc_handle) { + GPIO_InitTypeDef GPIO_InitStruct = {0}; + + RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0}; + PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_LTDC; + PeriphClkInitStruct.PLL3.PLL3M = 5; + PeriphClkInitStruct.PLL3.PLL3N = 40; + PeriphClkInitStruct.PLL3.PLL3P = 2; + PeriphClkInitStruct.PLL3.PLL3Q = 4; + PeriphClkInitStruct.PLL3.PLL3R = 4; + PeriphClkInitStruct.PLL3.PLL3RGE = RCC_PLL3VCIRANGE_2; + PeriphClkInitStruct.PLL3.PLL3VCOSEL = RCC_PLL3VCOWIDE; + PeriphClkInitStruct.PLL3.PLL3FRACN = 0; + + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { + boardctrl_fatal_error("HAL_LTDC_MspInit()"); + } + + if (ltdc_handle->Instance == LTDC) { + // LTDC and GPIO clock enable + __HAL_RCC_LTDC_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOI_CLK_ENABLE(); + __HAL_RCC_GPIOE_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); + __HAL_RCC_GPIOH_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + + GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_0 | GPIO_PIN_1 + | GPIO_PIN_2 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; + GPIO_InitStruct.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOI, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_10; + GPIO_InitStruct.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOF, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 + | GPIO_PIN_13 | GPIO_PIN_15; + GPIO_InitStruct.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOH, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7; + GPIO_InitStruct.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_8; + GPIO_InitStruct.Alternate = GPIO_AF13_LTDC; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + +#if defined(BOARD_V10) + GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_12; + GPIO_InitStruct.Alternate = GPIO_AF9_LTDC; + HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); +#else + GPIO_InitStruct.Pin = GPIO_PIN_2; + GPIO_InitStruct.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6; + GPIO_InitStruct.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOE, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_6; + GPIO_InitStruct.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOD, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_14; + GPIO_InitStruct.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOH, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14; + GPIO_InitStruct.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_4; + GPIO_InitStruct.Alternate = GPIO_AF14_LTDC; + HAL_GPIO_Init(GPIOI, &GPIO_InitStruct); +#endif + // LTDC interrupt + HAL_NVIC_SetPriority(LTDC_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(LTDC_IRQn); + } +} + +// HAL MSP de-initialization for the LTDC peripheral. +// Resets the LTDC block, disables its clock and de-initializes all +// associated GPIO pins. +void HAL_LTDC_MspDeInit(LTDC_HandleTypeDef *ltdc_handle) { + if (ltdc_handle->Instance == LTDC) { + __HAL_RCC_LTDC_FORCE_RESET(); + __HAL_RCC_LTDC_RELEASE_RESET(); + HAL_NVIC_DisableIRQ(LTDC_IRQn); + __HAL_RCC_LTDC_CLK_DISABLE(); + + HAL_GPIO_DeInit(GPIOI, GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_0 | GPIO_PIN_1 + | GPIO_PIN_2 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); + HAL_GPIO_DeInit(GPIOF, GPIO_PIN_10); + HAL_GPIO_DeInit(GPIOH, GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 + | GPIO_PIN_13 | GPIO_PIN_15); + HAL_GPIO_DeInit(GPIOA, GPIO_PIN_8); + HAL_GPIO_DeInit(GPIOG, GPIO_PIN_6 | GPIO_PIN_7); + +#if defined(BOARD_V10) + HAL_GPIO_DeInit(GPIOG, GPIO_PIN_10 | GPIO_PIN_12); +#else + HAL_GPIO_DeInit(GPIOA, GPIO_PIN_2); + HAL_GPIO_DeInit(GPIOE, GPIO_PIN_5 | GPIO_PIN_6); + HAL_GPIO_DeInit(GPIOD, GPIO_PIN_6); + HAL_GPIO_DeInit(GPIOH, GPIO_PIN_4 | GPIO_PIN_14); + HAL_GPIO_DeInit(GPIOG, GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14); + HAL_GPIO_DeInit(GPIOI, GPIO_PIN_4); +#endif + } +} diff --git a/driver/stm32/st_ltdc/board_config.py b/driver/stm32/st_ltdc/board_config.py new file mode 100644 index 0000000000..e27a449f46 --- /dev/null +++ b/driver/stm32/st_ltdc/board_config.py @@ -0,0 +1,2 @@ +BOARD = "DEV190806042" +VERSION = "" diff --git a/driver/stm32/st_ltdc/boards.py b/driver/stm32/st_ltdc/boards.py new file mode 100644 index 0000000000..58542f11ca --- /dev/null +++ b/driver/stm32/st_ltdc/boards.py @@ -0,0 +1,58 @@ +# boards.py +class TEMPLATE: + # LTDC + RST_PIN = "XX" # RGB-Pin 40 + BL_PIN = "XX" # RGB-Pin 34 + FB_ADDR = 0xC0000000 + # Touch (GT911) + TP_RST_PIN = "XX" # RGB-Pin 35 + TP_INT_PIN = "XX" # RGB-Pin 39 + TP_I2C_NUM = 0 # 0 = soft I2C + TP_SCL_PIN = "XX" # RGB-Pin 38 - Only if TP_I2C_NUM = 0 + TP_SDA_PIN = "XX" # RGB-Pin 36 - Only if TP_I2C_NUM = 0 + +class STM32H743_CORE_V10: + # LTDC + RST_PIN = "E2" + BL_PIN = "H6" + FB_ADDR = 0xC0000000 + # Touch (GT911) + TP_RST_PIN = "H7" + TP_INT_PIN = "E3" + TP_I2C_NUM = 4 + +class STM32H743_CORE_V13: + # LTDC + RST_PIN = "D11" + BL_PIN = "D12" + FB_ADDR = 0xC0000000 + # Touch (GT911) + TP_RST_PIN = "B14" + TP_INT_PIN = "H7" + TP_I2C_NUM = 0 + TP_SCL_PIN = "B12" + TP_SDA_PIN = "B13" + +class FK743M5_XIH6: + # LTDC + RST_PIN = "" + BL_PIN = "H6" + FB_ADDR = 0xC0000000 + # Touch (GT911) + TP_RST_PIN = "I10" + TP_INT_PIN = "I11" + TP_I2C_NUM = 0 + TP_SCL_PIN = "G3" + TP_SDA_PIN = "G7" + +class DEV190806042: + # LTDC + RST_PIN = "" + BL_PIN = "B5" + FB_ADDR = 0xC0000000 + # Touch (GT911) + TP_RST_PIN = "I8" + TP_INT_PIN = "H7" + TP_I2C_NUM = 0 + TP_SCL_PIN = "H6" + TP_SDA_PIN = "G7" diff --git a/driver/stm32/st_ltdc/display_conf.py b/driver/stm32/st_ltdc/display_conf.py new file mode 100644 index 0000000000..9d938ac3dd --- /dev/null +++ b/driver/stm32/st_ltdc/display_conf.py @@ -0,0 +1,26 @@ +# display_conf.py +try: + from board_config import BOARD, VERSION +except ImportError: + BOARD = "STM32H7_CORE" + VERSION = "V13" + +try: + from panel_config import PANEL +except ImportError: + PANEL = "IPS1024x600" + +if BOARD == "STM32H7_CORE": + if VERSION == "V10": + from boards import STM32H743_CORE_V10 as board + elif VERSION == "V13": + from boards import STM32H743_CORE_V13 as board +elif BOARD == "FK743M5-XIH6": + from boards import FK743M5_XIH6 as board +elif BOARD == "DEV190806042": + from boards import DEV190806042 as board + +if PANEL == "IPS1024x600": + from panels import IPS1024x600 as panel +elif PANEL == "RGB043M2": + from panels import RGB043M2 as panel diff --git a/driver/stm32/st_ltdc/display_driver.py b/driver/stm32/st_ltdc/display_driver.py new file mode 100644 index 0000000000..3219e523a3 --- /dev/null +++ b/driver/stm32/st_ltdc/display_driver.py @@ -0,0 +1,4 @@ +# display_driver.py +from st_ltdc_utils import init_display +from display_conf import board, panel +disp, touch = init_display(panel, board) diff --git a/driver/stm32/st_ltdc/lv_conf.h b/driver/stm32/st_ltdc/lv_conf.h new file mode 100644 index 0000000000..3a4dfdc9d9 --- /dev/null +++ b/driver/stm32/st_ltdc/lv_conf.h @@ -0,0 +1,1418 @@ +/** + * @file lv_conf.h + * Configuration file for v9.3.0 + */ + +/* + * Copy this file as `lv_conf.h` + * 1. simply next to the `lvgl` folder + * 2. or any other places and + * - define `LV_CONF_INCLUDE_SIMPLE` + * - add the path as include path + */ + +/* clang-format off */ +#if 1 /*Set it to "1" to enable content*/ + +#ifndef LV_CONF_H +#define LV_CONF_H + +/* If you need to include anything here, do it inside the `__ASSEMBLY__` guard */ +#if 0 && defined(__ASSEMBLY__) +#include "my_include.h" +#endif + +/*==================== + COLOR SETTINGS + *====================*/ + +/** Color depth: 1 (I1), 8 (L8), 16 (RGB565), 24 (RGB888), 32 (XRGB8888) */ +#define LV_COLOR_DEPTH 16 + +/*========================= + STDLIB WRAPPER SETTINGS + *=========================*/ + +/** Possible values + * - LV_STDLIB_BUILTIN: LVGL's built in implementation + * - LV_STDLIB_CLIB: Standard C functions, like malloc, strlen, etc + * - LV_STDLIB_MICROPYTHON: MicroPython implementation + * - LV_STDLIB_RTTHREAD: RT-Thread implementation + * - LV_STDLIB_CUSTOM: Implement the functions externally + */ + +#define LV_USE_STDLIB_MALLOC LV_STDLIB_MICROPYTHON +#define LV_USE_STDLIB_STRING LV_STDLIB_BUILTIN +#define LV_USE_STDLIB_SPRINTF LV_STDLIB_BUILTIN + +#define LV_STDINT_INCLUDE +#define LV_STDDEF_INCLUDE +#define LV_STDBOOL_INCLUDE +#define LV_INTTYPES_INCLUDE +#define LV_LIMITS_INCLUDE +#define LV_STDARG_INCLUDE + +#if LV_USE_STDLIB_MALLOC == LV_STDLIB_BUILTIN + /** Size of memory available for `lv_malloc()` in bytes (>= 2kB) */ + #define LV_MEM_SIZE (64 * 1024U) /**< [bytes] */ + + /** Size of the memory expand for `lv_malloc()` in bytes */ + #define LV_MEM_POOL_EXPAND_SIZE 0 + + /** Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too. */ + #define LV_MEM_ADR 0 /**< 0: unused*/ + /* Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc */ + #if LV_MEM_ADR == 0 + #undef LV_MEM_POOL_INCLUDE + #undef LV_MEM_POOL_ALLOC + #endif +#endif /*LV_USE_STDLIB_MALLOC == LV_STDLIB_BUILTIN*/ + +/*==================== + HAL SETTINGS + *====================*/ + +/** Default display refresh, input device read and animation step period. */ +#define LV_DEF_REFR_PERIOD 33 /**< [ms] */ + +/** Default Dots Per Inch. Used to initialize default sizes such as widgets sized, style paddings. + * (Not so important, you can adjust it to modify default sizes and spaces.) */ +#define LV_DPI_DEF 130 /**< [px/inch] */ + +/*================= + * OPERATING SYSTEM + *=================*/ +/** Select operating system to use. Possible options: + * - LV_OS_NONE + * - LV_OS_PTHREAD + * - LV_OS_FREERTOS + * - LV_OS_CMSIS_RTOS2 + * - LV_OS_RTTHREAD + * - LV_OS_WINDOWS + * - LV_OS_MQX + * - LV_OS_SDL2 + * - LV_OS_CUSTOM */ +#define LV_USE_OS LV_OS_NONE + +#if LV_USE_OS == LV_OS_CUSTOM + #define LV_OS_CUSTOM_INCLUDE +#endif +#if LV_USE_OS == LV_OS_FREERTOS + /* + * Unblocking an RTOS task with a direct notification is 45% faster and uses less RAM + * than unblocking a task using an intermediary object such as a binary semaphore. + * RTOS task notifications can only be used when there is only one task that can be the recipient of the event. + */ + #define LV_USE_FREERTOS_TASK_NOTIFY 1 +#endif + +/*======================== + * RENDERING CONFIGURATION + *========================*/ + +/** Align stride of all layers and images to this bytes */ +#define LV_DRAW_BUF_STRIDE_ALIGN 1 + +/** Align start address of draw_buf addresses to this bytes*/ +#define LV_DRAW_BUF_ALIGN 4 + +/** Using matrix for transformations. + * Requirements: + * - `LV_USE_MATRIX = 1`. + * - Rendering engine needs to support 3x3 matrix transformations. */ +#define LV_DRAW_TRANSFORM_USE_MATRIX 0 + +/* If a widget has `style_opa < 255` (not `bg_opa`, `text_opa` etc) or not NORMAL blend mode + * it is buffered into a "simple" layer before rendering. The widget can be buffered in smaller chunks. + * "Transformed layers" (if `transform_angle/zoom` are set) use larger buffers + * and can't be drawn in chunks. */ + +/** The target buffer size for simple layer chunks. */ +#define LV_DRAW_LAYER_SIMPLE_BUF_SIZE (24 * 1024) /**< [bytes]*/ + +/* Limit the max allocated memory for simple and transformed layers. + * It should be at least `LV_DRAW_LAYER_SIMPLE_BUF_SIZE` sized but if transformed layers are also used + * it should be enough to store the largest widget too (width x height x 4 area). + * Set it to 0 to have no limit. */ +/* #define LV_DRAW_LAYER_MAX_MEMORY 0 */ +/**< No limit by default [bytes]*/ + +/** Stack size of drawing thread. + * NOTE: If FreeType or ThorVG is enabled, it is recommended to set it to 32KB or more. + */ +/* #define LV_DRAW_THREAD_STACK_SIZE (8 * 1024) */ +/**< [bytes]*/ + +#define LV_USE_DRAW_SW 1 +#if LV_USE_DRAW_SW == 1 + /* + * Selectively disable color format support in order to reduce code size. + * NOTE: some features use certain color formats internally, e.g. + * - gradients use RGB888 + * - bitmaps with transparency may use ARGB8888 + */ + #define LV_DRAW_SW_SUPPORT_RGB565 1 + /* #define LV_DRAW_SW_SUPPORT_RGB565A8 1 */ + #define LV_DRAW_SW_SUPPORT_RGB888 1 + /* #define LV_DRAW_SW_SUPPORT_XRGB8888 1 */ + #define LV_DRAW_SW_SUPPORT_ARGB8888 1 + /* #define LV_DRAW_SW_SUPPORT_ARGB8888_PREMULTIPLIED 1 */ + /* #define LV_DRAW_SW_SUPPORT_L8 1 */ + /* #define LV_DRAW_SW_SUPPORT_AL88 1 */ + /* #define LV_DRAW_SW_SUPPORT_A8 1 */ + #define LV_DRAW_SW_SUPPORT_I1 1 + + /* The threshold of the luminance to consider a pixel as + * active in indexed color format */ + /* #define LV_DRAW_SW_I1_LUM_THRESHOLD 127 */ + + /** Set number of draw units. + * - > 1 requires operating system to be enabled in `LV_USE_OS`. + * - > 1 means multiple threads will render the screen in parallel. */ + #define LV_DRAW_SW_DRAW_UNIT_CNT 1 + + /** Use Arm-2D to accelerate software (sw) rendering. */ + #define LV_USE_DRAW_ARM2D_SYNC 0 + + /** Enable native helium assembly to be compiled. */ + #define LV_USE_NATIVE_HELIUM_ASM 0 + + /** + * - 0: Use a simple renderer capable of drawing only simple rectangles with gradient, images, text, and straight lines only. + * - 1: Use a complex renderer capable of drawing rounded corners, shadow, skew lines, and arcs too. */ + #define LV_DRAW_SW_COMPLEX 1 + + #if LV_DRAW_SW_COMPLEX == 1 + /** Allow buffering some shadow calculation. + * LV_DRAW_SW_SHADOW_CACHE_SIZE is the maximum shadow size to buffer, where shadow size is + * `shadow_width + radius`. Caching has LV_DRAW_SW_SHADOW_CACHE_SIZE^2 RAM cost. */ + #define LV_DRAW_SW_SHADOW_CACHE_SIZE 0 + + /** Set number of maximally-cached circle data. + * The circumference of 1/4 circle are saved for anti-aliasing. + * `radius * 4` bytes are used per circle (the most often used radiuses are saved). + * - 0: disables caching */ + #define LV_DRAW_SW_CIRCLE_CACHE_SIZE 4 + #endif + + #define LV_USE_DRAW_SW_ASM LV_DRAW_SW_ASM_NONE + + #if LV_USE_DRAW_SW_ASM == LV_DRAW_SW_ASM_CUSTOM + #define LV_DRAW_SW_ASM_CUSTOM_INCLUDE "" + #endif + + /** Enable drawing complex gradients in software: linear at an angle, radial or conical */ + /* #define LV_USE_DRAW_SW_COMPLEX_GRADIENTS 0 */ + +#endif + +/*Use TSi's aka (Think Silicon) NemaGFX */ +#define LV_USE_NEMA_GFX 0 + +#if LV_USE_NEMA_GFX + /** Select which NemaGFX HAL to use. Possible options: + * - LV_NEMA_HAL_CUSTOM + * - LV_NEMA_HAL_STM32 */ + #define LV_USE_NEMA_HAL LV_NEMA_HAL_CUSTOM + #if LV_USE_NEMA_HAL == LV_NEMA_HAL_STM32 + #define LV_NEMA_STM32_HAL_INCLUDE + #endif + + /*Enable Vector Graphics Operations. Available only if NemaVG library is present*/ + #define LV_USE_NEMA_VG 0 + #if LV_USE_NEMA_VG + /*Define application's resolution used for VG related buffer allocation */ + #define LV_NEMA_GFX_MAX_RESX 800 + #define LV_NEMA_GFX_MAX_RESY 600 + #endif +#endif + +/** Use NXP's VG-Lite GPU on iMX RTxxx platforms. */ +#define LV_USE_DRAW_VGLITE 0 + +#if LV_USE_DRAW_VGLITE + /** Enable blit quality degradation workaround recommended for screen's dimension > 352 pixels. */ + #define LV_USE_VGLITE_BLIT_SPLIT 0 + + #if LV_USE_OS + /** Use additional draw thread for VG-Lite processing. */ + #define LV_USE_VGLITE_DRAW_THREAD 1 + + #if LV_USE_VGLITE_DRAW_THREAD + /** Enable VGLite draw async. Queue multiple tasks and flash them once to the GPU. */ + #define LV_USE_VGLITE_DRAW_ASYNC 1 + #endif + #endif + + /** Enable VGLite asserts. */ + #define LV_USE_VGLITE_ASSERT 0 +#endif + +/** Use NXP's PXP on iMX RTxxx platforms. */ +#define LV_USE_PXP 0 + +#if LV_USE_PXP + /** Use PXP for drawing.*/ + #define LV_USE_DRAW_PXP 1 + + /** Use PXP to rotate display.*/ + #define LV_USE_ROTATE_PXP 0 + + #if LV_USE_DRAW_PXP && LV_USE_OS + /** Use additional draw thread for PXP processing.*/ + #define LV_USE_PXP_DRAW_THREAD 1 + #endif + + /** Enable PXP asserts. */ + #define LV_USE_PXP_ASSERT 0 +#endif + +/** Use NXP's G2D on MPU platforms. */ +#define LV_USE_DRAW_G2D 0 + +#if LV_USE_DRAW_G2D + /** Maximum number of buffers that can be stored for G2D draw unit. + * Includes the frame buffers and assets. */ + #define LV_G2D_HASH_TABLE_SIZE 50 + + #if LV_USE_OS + /** Use additional draw thread for G2D processing.*/ + #define LV_USE_G2D_DRAW_THREAD 1 + #endif + + /** Enable G2D asserts. */ + #define LV_USE_G2D_ASSERT 0 +#endif + +/** Use Renesas Dave2D on RA platforms. */ +#define LV_USE_DRAW_DAVE2D 0 + +/** Draw using cached SDL textures*/ +#define LV_USE_DRAW_SDL 0 + +/** Use VG-Lite GPU. */ +#define LV_USE_DRAW_VG_LITE 0 + +#if LV_USE_DRAW_VG_LITE + /** Enable VG-Lite custom external 'gpu_init()' function */ + #define LV_VG_LITE_USE_GPU_INIT 0 + + /** Enable VG-Lite assert. */ + #define LV_VG_LITE_USE_ASSERT 0 + + /** VG-Lite flush commit trigger threshold. GPU will try to batch these many draw tasks. */ + #define LV_VG_LITE_FLUSH_MAX_COUNT 8 + + /** Enable border to simulate shadow. + * NOTE: which usually improves performance, + * but does not guarantee the same rendering quality as the software. */ + #define LV_VG_LITE_USE_BOX_SHADOW 0 + + /** VG-Lite gradient maximum cache number. + * @note The memory usage of a single gradient image is 4K bytes. */ + #define LV_VG_LITE_GRAD_CACHE_CNT 32 + + /** VG-Lite stroke maximum cache number. */ + #define LV_VG_LITE_STROKE_CACHE_CNT 32 +#endif + + +/** Accelerate blends, fills, etc. with STM32 DMA2D */ +#define LV_USE_DRAW_DMA2D 0 + +#if LV_USE_DRAW_DMA2D + #define LV_DRAW_DMA2D_HAL_INCLUDE "stm32h7xx_hal.h" + + /* if enabled, the user is required to call `lv_draw_dma2d_transfer_complete_interrupt_handler` + * upon receiving the DMA2D global interrupt + */ + #define LV_USE_DRAW_DMA2D_INTERRUPT 0 +#endif + +/** Draw using cached OpenGLES textures */ +#define LV_USE_DRAW_OPENGLES 0 +/*======================= + * FEATURE CONFIGURATION + *=======================*/ + +/*------------- + * Logging + *-----------*/ + +/*Enable the log module*/ + +#ifdef MICROPY_LV_USE_LOG + #define LV_USE_LOG MICROPY_LV_USE_LOG +#else + #define LV_USE_LOG 0 +#endif + +#if LV_USE_LOG + /** Set value to one of the following levels of logging detail: + * - LV_LOG_LEVEL_TRACE Log detailed information. + * - LV_LOG_LEVEL_INFO Log important events. + * - LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem. + * - LV_LOG_LEVEL_ERROR Log only critical issues, when system may fail. + * - LV_LOG_LEVEL_USER Log only custom log messages added by the user. + * - LV_LOG_LEVEL_NONE Do not log anything. */ + #define LV_LOG_LEVEL LV_LOG_LEVEL_INFO + + /** - 1: Print log with 'printf'; + * - 0: User needs to register a callback with `lv_log_register_print_cb()`. */ + #define LV_LOG_PRINTF 0 + + /** Set callback to print logs. + * E.g `my_print`. The prototype should be `void my_print(lv_log_level_t level, const char * buf)`. + * Can be overwritten by `lv_log_register_print_cb`. */ + + #define LV_LOG_PRINT_CB mp_lv_log_cb + + /** - 1: Enable printing timestamp; + * - 0: Disable printing timestamp. */ + #define LV_LOG_USE_TIMESTAMP 1 + + /** - 1: Print file and line number of the log; + * - 0: Do not print file and line number of the log. */ + #define LV_LOG_USE_FILE_LINE 1 + + /* Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs. */ + #define LV_LOG_TRACE_MEM 1 /**< Enable/disable trace logs in memory operations. */ + #define LV_LOG_TRACE_TIMER 1 /**< Enable/disable trace logs in timer operations. */ + #define LV_LOG_TRACE_INDEV 1 /**< Enable/disable trace logs in input device operations. */ + #define LV_LOG_TRACE_DISP_REFR 1 /**< Enable/disable trace logs in display re-draw operations. */ + #define LV_LOG_TRACE_EVENT 1 /**< Enable/disable trace logs in event dispatch logic. */ + #define LV_LOG_TRACE_OBJ_CREATE 1 /**< Enable/disable trace logs in object creation (core `obj` creation plus every widget). */ + #define LV_LOG_TRACE_LAYOUT 1 /**< Enable/disable trace logs in flex- and grid-layout operations. */ + #define LV_LOG_TRACE_ANIM 1 /**< Enable/disable trace logs in animation logic. */ + #define LV_LOG_TRACE_CACHE 1 /**< Enable/disable trace logs in cache operations. */ +#endif /*LV_USE_LOG*/ + +/*------------- + * Asserts + *-----------*/ + +/* Enable assertion failures if an operation fails or invalid data is found. + * If LV_USE_LOG is enabled, an error message will be printed on failure. */ +#define LV_USE_ASSERT_NULL 1 /**< Check if the parameter is NULL. (Very fast, recommended) */ +#define LV_USE_ASSERT_MALLOC 1 /**< Checks is the memory is successfully allocated or no. (Very fast, recommended) */ +#define LV_USE_ASSERT_STYLE 0 /**< Check if the styles are properly initialized. (Very fast, recommended) */ +#define LV_USE_ASSERT_MEM_INTEGRITY 0 /**< Check the integrity of `lv_mem` after critical operations. (Slow) */ +#define LV_USE_ASSERT_OBJ 0 /**< Check the object's type and existence (e.g. not deleted). (Slow) */ + +/** Add a custom handler when assert happens e.g. to restart MCU. */ +#define LV_ASSERT_HANDLER_INCLUDE +#define LV_ASSERT_HANDLER while(1); /**< Halt by default */ + +/*------------- + * Debug + *-----------*/ + +/** 1: Draw random colored rectangles over the redrawn areas. */ +#define LV_USE_REFR_DEBUG 0 + +/** 1: Draw a red overlay for ARGB layers and a green overlay for RGB layers*/ +#define LV_USE_LAYER_DEBUG 0 + +/** 1: Adds the following behaviors for debugging: + * - Draw overlays with different colors for each draw_unit's tasks. + * - Draw index number of draw unit on white background. + * - For layers, draws index number of draw unit on black background. */ +#define LV_USE_PARALLEL_DRAW_DEBUG 0 + +/*------------- + * Others + *-----------*/ + +/* PRIVATE API */ + +#define LV_USE_PRIVATE_API 1 + +/*Garbage Collector settings + *Used if LVGL is bound to higher level language and the memory is managed by that language*/ +extern void mp_lv_init_gc(); +extern void mp_lv_deinit_gc(); +#define LV_GC_INIT() mp_lv_init_gc() +#define LV_GC_DEINIT() mp_lv_deinit_gc() + +#define LV_ENABLE_GLOBAL_CUSTOM 1 +#if LV_ENABLE_GLOBAL_CUSTOM + extern void *mp_lv_roots; + #define LV_GLOBAL_CUSTOM() ((lv_global_t*)mp_lv_roots) +#endif + +/** Default cache size in bytes. + * Used by image decoders such as `lv_lodepng` to keep the decoded image in memory. + * If size is not set to 0, the decoder will fail to decode when the cache is full. + * If size is 0, the cache function is not enabled and the decoded memory will be + * released immediately after use. */ +#ifdef MICROPY_CACHE_SIZE + #define LV_CACHE_DEF_SIZE MICROPY_CACHE_SIZE +#else + #define LV_CACHE_DEF_SIZE 0 +#endif + +/** Default number of image header cache entries. The cache is used to store the headers of images + * The main logic is like `LV_CACHE_DEF_SIZE` but for image headers. */ +#ifdef MICROPY_IMAGE_HEADER_CACHE_COUNT + #define LV_IMAGE_HEADER_CACHE_DEF_CNT MICROPY_IMAGE_HEADER_CACHE_COUNT +#else + #define LV_IMAGE_HEADER_CACHE_DEF_CNT 32 +#endif + + +/** Number of stops allowed per gradient. Increase this to allow more stops. + * This adds (sizeof(lv_color_t) + 1) bytes per additional stop. */ +#define LV_GRADIENT_MAX_STOPS 2 + +/** Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently. + * - 0: round down, + * - 64: round up from x.75, + * - 128: round up from half, + * - 192: round up from x.25, + * - 254: round up */ +#define LV_COLOR_MIX_ROUND_OFS 0 + +/** Add 2 x 32-bit variables to each `lv_obj_t` to speed up getting style properties */ +#define LV_OBJ_STYLE_CACHE 1 + +/** Add `id` field to `lv_obj_t` */ +#define LV_USE_OBJ_ID 0 + +/** Enable support widget names*/ +#define LV_USE_OBJ_NAME 0 + +/** Automatically assign an ID when obj is created */ +#define LV_OBJ_ID_AUTO_ASSIGN LV_USE_OBJ_ID + +/** Use builtin obj ID handler functions: +* - lv_obj_assign_id: Called when a widget is created. Use a separate counter for each widget class as an ID. +* - lv_obj_id_compare: Compare the ID to decide if it matches with a requested value. +* - lv_obj_stringify_id: Return string-ified identifier, e.g. "button3". +* - lv_obj_free_id: Does nothing, as there is no memory allocation for the ID. +* When disabled these functions needs to be implemented by the user.*/ +#define LV_USE_OBJ_ID_BUILTIN 0 + +/** Use obj property set/get API. */ +#define LV_USE_OBJ_PROPERTY 0 + +/** Enable property name support. */ +#define LV_USE_OBJ_PROPERTY_NAME 0 + +/* Use VG-Lite Simulator. + * - Requires: LV_USE_THORVG_INTERNAL or LV_USE_THORVG_EXTERNAL */ +#define LV_USE_VG_LITE_THORVG 0 + +#if LV_USE_VG_LITE_THORVG + /** Enable LVGL's blend mode support */ + #define LV_VG_LITE_THORVG_LVGL_BLEND_SUPPORT 0 + + /** Enable YUV color format support */ + #define LV_VG_LITE_THORVG_YUV_SUPPORT 0 + + /** Enable Linear gradient extension support */ + #define LV_VG_LITE_THORVG_LINEAR_GRADIENT_EXT_SUPPORT 0 + + /** Enable alignment on 16 pixels */ + #define LV_VG_LITE_THORVG_16PIXELS_ALIGN 1 + + /** Buffer address alignment */ + #define LV_VG_LITE_THORVG_BUF_ADDR_ALIGN 64 + + /** Enable multi-thread render */ + #define LV_VG_LITE_THORVG_THREAD_RENDER 0 +#endif + +/* Enable the multi-touch gesture recognition feature */ +/* Gesture recognition requires the use of floats */ +#define LV_USE_GESTURE_RECOGNITION 0 + +/*===================== + * COMPILER SETTINGS + *====================*/ + +/** For big endian systems set to 1 */ +#define LV_BIG_ENDIAN_SYSTEM 0 + +/** Define a custom attribute for `lv_tick_inc` function */ +#define LV_ATTRIBUTE_TICK_INC + +/** Define a custom attribute for `lv_timer_handler` function */ +#define LV_ATTRIBUTE_TIMER_HANDLER + +/** Define a custom attribute for `lv_display_flush_ready` function */ +#define LV_ATTRIBUTE_FLUSH_READY + +/** Align VG_LITE buffers on this number of bytes. + * @note vglite_src_buf_aligned() uses this value to validate alignment of passed buffer pointers. */ +#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1 + +/** Will be added where memory needs to be aligned (with -Os data might not be aligned to boundary by default). + * E.g. __attribute__((aligned(4)))*/ +#define LV_ATTRIBUTE_MEM_ALIGN + +/** Attribute to mark large constant arrays, for example for font bitmaps */ +#define LV_ATTRIBUTE_LARGE_CONST + +/** Compiler prefix for a large array declaration in RAM */ +#define LV_ATTRIBUTE_LARGE_RAM_ARRAY + +/** Place performance critical functions into a faster memory (e.g RAM) */ +#define LV_ATTRIBUTE_FAST_MEM + +/** Export integer constant to binding. This macro is used with constants in the form of LV_ that + * should also appear on LVGL binding API such as MicroPython. */ +/* #define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning */ +/**< The default value just prevents GCC warning */ + +#define LV_EXPORT_CONST_INT(int_value) enum {ENUM_##int_value = int_value} + +/** Prefix all global extern data with this */ +#define LV_ATTRIBUTE_EXTERN_DATA + +/** Use `float` as `lv_value_precise_t` */ +#define LV_USE_FLOAT 0 + +/** Enable matrix support + * - Requires `LV_USE_FLOAT = 1` */ +#define LV_USE_MATRIX 0 + +/** Include `lvgl_private.h` in `lvgl.h` to access internal data and functions by default */ +#ifndef LV_USE_PRIVATE_API + #define LV_USE_PRIVATE_API 1 +#endif + +/*================== + * FONT USAGE + *===================*/ + +/* Montserrat fonts with ASCII range and some symbols using bpp = 4 + * https://fonts.google.com/specimen/Montserrat */ +#define LV_FONT_MONTSERRAT_8 1 +#define LV_FONT_MONTSERRAT_10 1 +#define LV_FONT_MONTSERRAT_12 1 +#define LV_FONT_MONTSERRAT_14 1 +#define LV_FONT_MONTSERRAT_16 1 +#define LV_FONT_MONTSERRAT_18 1 +#define LV_FONT_MONTSERRAT_20 1 +#define LV_FONT_MONTSERRAT_22 1 +#define LV_FONT_MONTSERRAT_24 1 +#define LV_FONT_MONTSERRAT_26 1 +#define LV_FONT_MONTSERRAT_28 1 +#define LV_FONT_MONTSERRAT_30 1 +#define LV_FONT_MONTSERRAT_32 1 +#define LV_FONT_MONTSERRAT_34 1 +#define LV_FONT_MONTSERRAT_36 1 +#define LV_FONT_MONTSERRAT_38 1 +#define LV_FONT_MONTSERRAT_40 1 +#define LV_FONT_MONTSERRAT_42 1 +#define LV_FONT_MONTSERRAT_44 1 +#define LV_FONT_MONTSERRAT_46 1 +#define LV_FONT_MONTSERRAT_48 1 + +/* Demonstrate special features */ +#define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /**< bpp = 3 */ +#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /**< Hebrew, Arabic, Persian letters and all their forms */ +/* #define LV_FONT_SIMSUN_14_CJK 0 */ +/**< 1000 most common CJK radicals */ +#define LV_FONT_SIMSUN_16_CJK 0 /**< 1000 most common CJK radicals */ +/* #define LV_FONT_SOURCE_HAN_SANS_SC_14_CJK 0 */ +/**< 1338 most common CJK radicals */ +/* #define LV_FONT_SOURCE_HAN_SANS_SC_16_CJK 0 */ +/**< 1338 most common CJK radicals */ + +/** Pixel perfect monospaced fonts */ +#define LV_FONT_UNSCII_8 1 +#define LV_FONT_UNSCII_16 1 + +/** Optionally declare custom fonts here. + * + * You can use any of these fonts as the default font too and they will be available + * globally. Example: + * + * @code + * #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2) + * @endcode + */ +#define LV_FONT_CUSTOM_DECLARE + +/** Always set a default font */ +#define LV_FONT_DEFAULT &lv_font_montserrat_14 + +/** Enable handling large font and/or fonts with a lot of characters. + * The limit depends on the font size, font face and bpp. + * A compiler error will be triggered if a font needs it. */ +#define LV_FONT_FMT_TXT_LARGE 1 + +/** Enables/disables support for compressed fonts. */ +#define LV_USE_FONT_COMPRESSED 1 + +/** Enable drawing placeholders when glyph dsc is not found. */ +#define LV_USE_FONT_PLACEHOLDER 1 + +/*================= + * TEXT SETTINGS + *=================*/ + +/** + * Select a character encoding for strings. + * Your IDE or editor should have the same character encoding. + * - LV_TXT_ENC_UTF8 + * - LV_TXT_ENC_ASCII + */ +#define LV_TXT_ENC LV_TXT_ENC_UTF8 + +/** While rendering text strings, break (wrap) text on these chars. */ +#define LV_TXT_BREAK_CHARS " ,.;:-_)]}" + +/** If a word is at least this long, will break wherever "prettiest". + * To disable, set to a value <= 0. */ +#define LV_TXT_LINE_BREAK_LONG_LEN 0 + +/** Minimum number of characters in a long word to put on a line before a break. + * Depends on LV_TXT_LINE_BREAK_LONG_LEN. */ +#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3 + +/** Minimum number of characters in a long word to put on a line after a break. + * Depends on LV_TXT_LINE_BREAK_LONG_LEN. */ +#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3 + +/** Support bidirectional text. Allows mixing Left-to-Right and Right-to-Left text. + * The direction will be processed according to the Unicode Bidirectional Algorithm: + * https://www.w3.org/International/articles/inline-bidi-markup/uba-basics */ +#define LV_USE_BIDI 0 +#if LV_USE_BIDI + /*Set the default direction. Supported values: + *`LV_BASE_DIR_LTR` Left-to-Right + *`LV_BASE_DIR_RTL` Right-to-Left + *`LV_BASE_DIR_AUTO` detect text base direction*/ + #define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO +#endif + +/** Enable Arabic/Persian processing + * In these languages characters should be replaced with another form based on their position in the text */ +#define LV_USE_ARABIC_PERSIAN_CHARS 0 + +/*The control character to use for signaling text recoloring*/ +#define LV_TXT_COLOR_CMD "#" + +/*================== + * WIDGETS + *================*/ +/* Documentation for widgets can be found here: https://docs.lvgl.io/master/details/widgets/index.html . */ + +/** 1: Causes these widgets to be given default values at creation time. + * - lv_buttonmatrix_t: Get default maps: {"Btn1", "Btn2", "Btn3", "\n", "Btn4", "Btn5", ""}, else map not set. + * - lv_checkbox_t : String label set to "Check box", else set to empty string. + * - lv_dropdown_t : Options set to "Option 1", "Option 2", "Option 3", else no values are set. + * - lv_roller_t : Options set to "Option 1", "Option 2", "Option 3", "Option 4", "Option 5", else no values are set. + * - lv_label_t : Text set to "Text", else empty string. + * */ +#define LV_WIDGETS_HAS_DEFAULT_VALUE 0 + +#define LV_USE_ANIMIMG 1 + +#define LV_USE_ARC 1 + +#define LV_USE_ARCLABEL 1 + +#define LV_USE_BAR 1 + +#define LV_USE_BUTTON 1 + +#define LV_USE_BUTTONMATRIX 1 + +#define LV_USE_CALENDAR 1 +#if LV_USE_CALENDAR + #define LV_CALENDAR_WEEK_STARTS_MONDAY 0 + #if LV_CALENDAR_WEEK_STARTS_MONDAY + #define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"} + #else + #define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"} + #endif + + #define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} + #define LV_USE_CALENDAR_HEADER_ARROW 1 + #define LV_USE_CALENDAR_HEADER_DROPDOWN 1 + #define LV_USE_CALENDAR_CHINESE 0 +#endif /*LV_USE_CALENDAR*/ + +#define LV_USE_CANVAS 1 + +#define LV_USE_CHART 1 + +#define LV_USE_CHECKBOX 1 + +#define LV_USE_DROPDOWN 1 /**< Requires: lv_label */ + +#define LV_USE_IMAGE 1 /**< Requires: lv_label */ + +#define LV_USE_IMAGEBUTTON 1 + +#define LV_USE_KEYBOARD 1 + +#define LV_USE_LABEL 1 +#if LV_USE_LABEL + #define LV_LABEL_TEXT_SELECTION 1 /**< Enable selecting text of the label */ + #define LV_LABEL_LONG_TXT_HINT 1 /**< Store some extra info in labels to speed up drawing of very long text */ + #define LV_LABEL_WAIT_CHAR_COUNT 3 /**< The count of wait chart */ +#endif + +#define LV_USE_LED 1 + +#define LV_USE_LINE 1 + +#define LV_USE_LIST 1 + +#define LV_USE_LOTTIE 0 /**< Requires: lv_canvas, thorvg */ + +#define LV_USE_MENU 1 + +#define LV_USE_MSGBOX 1 + +#define LV_USE_ROLLER 1 /**< Requires: lv_label */ + +#define LV_USE_SCALE 1 + +#define LV_USE_SLIDER 1 /**< Requires: lv_bar */ + +#define LV_USE_SPAN 1 +#if LV_USE_SPAN + /** A line of text can contain this maximum number of span descriptors. */ + #define LV_SPAN_SNIPPET_STACK_SIZE 128 +#endif + +#define LV_USE_SPINBOX 1 + +#define LV_USE_SPINNER 1 + +#define LV_USE_SWITCH 1 + +#define LV_USE_TABLE 1 + +#define LV_USE_TABVIEW 1 + +#define LV_USE_TEXTAREA 1 /**< Requires: lv_label */ +#if LV_USE_TEXTAREA != 0 + #define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /**< [ms] */ +#endif + +#define LV_USE_TILEVIEW 1 + +#define LV_USE_WIN 1 + +#define LV_USE_3DTEXTURE 0 + +/*================== + * THEMES + *==================*/ +/* Documentation for themes can be found here: https://docs.lvgl.io/master/details/common-widget-features/styles/style.html#themes . */ + +/** A simple, impressive and very complete theme */ +#define LV_USE_THEME_DEFAULT 1 +#if LV_USE_THEME_DEFAULT + /** 0: Light mode; 1: Dark mode */ + #define LV_THEME_DEFAULT_DARK 0 + + /** 1: Enable grow on press */ + #define LV_THEME_DEFAULT_GROW 1 + + /** Default transition time in ms. */ + #define LV_THEME_DEFAULT_TRANSITION_TIME 80 +#endif /*LV_USE_THEME_DEFAULT*/ + +/** A very simple theme that is a good starting point for a custom theme */ +#define LV_USE_THEME_SIMPLE 0 + +/** A theme designed for monochrome displays */ +#define LV_USE_THEME_MONO 0 + +/*================== + * LAYOUTS + *==================*/ +/* Documentation for layouts can be found here: https://docs.lvgl.io/master/details/common-widget-features/layouts/index.html . */ + +/** A layout similar to Flexbox in CSS. */ +#define LV_USE_FLEX 1 + +/** A layout similar to Grid in CSS. */ +#define LV_USE_GRID 1 + +/*==================== + * 3RD PARTS LIBRARIES + *====================*/ +/* Documentation for libraries can be found here: https://docs.lvgl.io/master/details/libs/index.html . */ + +/* File system interfaces for common APIs */ + +/** Setting a default driver letter allows skipping the driver prefix in filepaths. + * Documentation about how to use the below driver-identifier letters can be found at + * https://docs.lvgl.io/master/details/main-modules/fs.html#lv-fs-identifier-letters . */ +/* #define LV_FS_DEFAULT_DRIVER_LETTER '\0' */ + +/** API for fopen, fread, etc. */ +#define LV_USE_FS_STDIO 0 +#if LV_USE_FS_STDIO + #define LV_FS_STDIO_LETTER '\0' /**< Set an upper-case driver-identifier letter for this driver (e.g. 'A'). */ + #define LV_FS_STDIO_PATH "" /**< Set the working directory. File/directory paths will be appended to it. */ + #define LV_FS_STDIO_CACHE_SIZE 0 /**< >0 to cache this number of bytes in lv_fs_read() */ +#endif + +/** API for open, read, etc. */ +#define LV_USE_FS_POSIX 0 +#if LV_USE_FS_POSIX + #define LV_FS_POSIX_LETTER '\0' /**< Set an upper-case driver-identifier letter for this driver (e.g. 'A'). */ + #define LV_FS_POSIX_PATH "" /**< Set the working directory. File/directory paths will be appended to it. */ + #define LV_FS_POSIX_CACHE_SIZE 0 /**< >0 to cache this number of bytes in lv_fs_read() */ +#endif + +/** API for CreateFile, ReadFile, etc. */ +#define LV_USE_FS_WIN32 0 +#if LV_USE_FS_WIN32 + #define LV_FS_WIN32_LETTER '\0' /**< Set an upper-case driver-identifier letter for this driver (e.g. 'A'). */ + #define LV_FS_WIN32_PATH "" /**< Set the working directory. File/directory paths will be appended to it. */ + #define LV_FS_WIN32_CACHE_SIZE 0 /**< >0 to cache this number of bytes in lv_fs_read() */ +#endif + +/** API for FATFS (needs to be added separately). Uses f_open, f_read, etc. */ +#define LV_USE_FS_FATFS 0 +#if LV_USE_FS_FATFS + #define LV_FS_FATFS_LETTER '\0' /**< Set an upper-case driver-identifier letter for this driver (e.g. 'A'). */ + #define LV_FS_FATFS_PATH "" /**< Set the working directory. File/directory paths will be appended to it. */ + #define LV_FS_FATFS_CACHE_SIZE 0 /**< >0 to cache this number of bytes in lv_fs_read() */ +#endif + +/** API for memory-mapped file access. */ +#define LV_USE_FS_MEMFS 1 +#if LV_USE_FS_MEMFS + #define LV_FS_MEMFS_LETTER 'M' /**< Set an upper-case driver-identifier letter for this driver (e.g. 'A'). */ +#endif + +/** API for LittleFs. */ +#define LV_USE_FS_LITTLEFS 0 +#if LV_USE_FS_LITTLEFS + #define LV_FS_LITTLEFS_LETTER '\0' /**< Set an upper-case driver-identifier letter for this driver (e.g. 'A'). */ + #define LV_FS_LITTLEFS_PATH "" /**< Set the working directory. File/directory paths will be appended to it. */ +#endif + +/** API for Arduino LittleFs. */ +#define LV_USE_FS_ARDUINO_ESP_LITTLEFS 0 +#if LV_USE_FS_ARDUINO_ESP_LITTLEFS + #define LV_FS_ARDUINO_ESP_LITTLEFS_LETTER '\0' /**< Set an upper-case driver-identifier letter for this driver (e.g. 'A'). */ + #define LV_FS_ARDUINO_ESP_LITTLEFS_PATH "" /**< Set the working directory. File/directory paths will be appended to it. */ +#endif + +/** API for Arduino Sd. */ +#define LV_USE_FS_ARDUINO_SD 0 +#if LV_USE_FS_ARDUINO_SD + #define LV_FS_ARDUINO_SD_LETTER '\0' /**< Set an upper-case driver-identifier letter for this driver (e.g. 'A'). */ + #define LV_FS_ARDUINO_SD_PATH "" /**< Set the working directory. File/directory paths will be appended to it. */ +#endif + +/** API for UEFI */ +#define LV_USE_FS_UEFI 0 +#if LV_USE_FS_UEFI + #define LV_FS_UEFI_LETTER '\0' /**< Set an upper-case driver-identifier letter for this driver (e.g. 'A'). */ +#endif + +/** LODEPNG decoder library */ +#define LV_USE_LODEPNG 1 + +/** PNG decoder(libpng) library */ +#define LV_USE_LIBPNG 0 + +/** BMP decoder library */ +#define LV_USE_BMP 0 + +/** JPG + split JPG decoder library. + * Split JPG is a custom format optimized for embedded systems. */ +#define LV_USE_TJPGD 1 + +/** libjpeg-turbo decoder library. + * - Supports complete JPEG specifications and high-performance JPEG decoding. */ +#define LV_USE_LIBJPEG_TURBO 0 + +/** GIF decoder library */ +#define LV_USE_GIF 1 +#if LV_USE_GIF + /** GIF decoder accelerate */ + #define LV_GIF_CACHE_DECODE_DATA 0 +#endif + + +/** Decode bin images to RAM */ +#define LV_BIN_DECODER_RAM_LOAD 0 + +/** RLE decompress library */ +#define LV_USE_RLE 0 + +/** QR code library */ +#define LV_USE_QRCODE 1 + +/** Barcode code library */ +#define LV_USE_BARCODE 1 + +/*FreeType library*/ +#ifdef MICROPY_FREETYPE + #define LV_USE_FREETYPE MICROPY_FREETYPE +#else + #define LV_USE_FREETYPE 0 +#endif + +#if LV_USE_FREETYPE + /** Let FreeType use LVGL memory and file porting */ + #define LV_FREETYPE_USE_LVGL_PORT 0 + + /** Cache count of glyphs in FreeType, i.e. number of glyphs that can be cached. + * The higher the value, the more memory will be used. */ + #define LV_FREETYPE_CACHE_FT_GLYPH_CNT 256 +#endif + +/** Built-in TTF decoder */ +#define LV_USE_TINY_TTF 0 +#if LV_USE_TINY_TTF + /* Enable loading TTF data from files */ + #define LV_TINY_TTF_FILE_SUPPORT 0 + #define LV_TINY_TTF_CACHE_GLYPH_CNT 256 +#endif + +/** Rlottie library */ +#ifdef MICROPY_RLOTTIE + #define LV_USE_RLOTTIE MICROPY_RLOTTIE +#else + #define LV_USE_RLOTTIE 0 +#endif + +/** Enable Vector Graphic APIs + * - Requires `LV_USE_MATRIX = 1` */ +#define LV_USE_VECTOR_GRAPHIC 0 + +/** Enable ThorVG (vector graphics library) from the src/libs folder */ +#define LV_USE_THORVG_INTERNAL 0 + +/** Enable ThorVG by assuming that its installed and linked to the project */ +#define LV_USE_THORVG_EXTERNAL 0 + +/** Use lvgl built-in LZ4 lib */ +#define LV_USE_LZ4_INTERNAL 0 + +/** Use external LZ4 library */ +#define LV_USE_LZ4_EXTERNAL 0 + +/*SVG library + * - Requires `LV_USE_VECTOR_GRAPHIC = 1` */ +#define LV_USE_SVG 0 +#define LV_USE_SVG_ANIMATION 0 +#define LV_USE_SVG_DEBUG 0 + +/** FFmpeg library for image decoding and playing videos. + * Supports all major image formats so do not enable other image decoder with it. */ + +#ifdef MICROPY_FFMPEG + #define LV_USE_FFMPEG MICROPY_FFMPEG +#else + #define LV_USE_FFMPEG 0 +#endif +#if LV_USE_FFMPEG + /** Dump input information to stderr */ + #define LV_FFMPEG_DUMP_FORMAT 0 + /** Use lvgl file path in FFmpeg Player widget + * You won't be able to open URLs after enabling this feature. + * Note that FFmpeg image decoder will always use lvgl file system. */ + #define LV_FFMPEG_PLAYER_USE_LV_FS 0 +#endif + +/*================== + * OTHERS + *==================*/ +/* Documentation for several of the below items can be found here: https://docs.lvgl.io/master/details/auxiliary-modules/index.html . */ + +/** 1: Enable API to take snapshot for object */ +#define LV_USE_SNAPSHOT 1 + +/** 1: Enable system monitor component */ +#define LV_USE_SYSMON 0 +#if LV_USE_SYSMON + /** Get the idle percentage. E.g. uint32_t my_get_idle(void); */ + /* #define LV_SYSMON_GET_IDLE lv_os_get_idle_percent */ + #define LV_SYSMON_GET_IDLE lv_timer_get_idle + + /** 1: Show CPU usage and FPS count. + * - Requires `LV_USE_SYSMON = 1` */ + #define LV_USE_PERF_MONITOR 1 + #if LV_USE_PERF_MONITOR + #define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT + + /** 0: Displays performance data on the screen; 1: Prints performance data using log. */ + #define LV_USE_PERF_MONITOR_LOG_MODE 0 + #endif + + /** 1: Show used memory and memory fragmentation. + * - Requires `LV_USE_STDLIB_MALLOC = LV_STDLIB_BUILTIN` + * - Requires `LV_USE_SYSMON = 1`*/ + #define LV_USE_MEM_MONITOR 0 + #if LV_USE_MEM_MONITOR + #define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT + #endif +#endif /*LV_USE_SYSMON*/ + +/** 1: Enable runtime performance profiler */ +#define LV_USE_PROFILER 0 +#if LV_USE_PROFILER + /** 1: Enable the built-in profiler */ + #define LV_USE_PROFILER_BUILTIN 1 + #if LV_USE_PROFILER_BUILTIN + /** Default profiler trace buffer size */ + #define LV_PROFILER_BUILTIN_BUF_SIZE (16 * 1024) /**< [bytes] */ + #define LV_PROFILER_BUILTIN_DEFAULT_ENABLE 1 + #endif + + /** Header to include for profiler */ + #define LV_PROFILER_INCLUDE "lvgl/src/misc/lv_profiler_builtin.h" + + /** Profiler start point function */ + #define LV_PROFILER_BEGIN LV_PROFILER_BUILTIN_BEGIN + + /** Profiler end point function */ + #define LV_PROFILER_END LV_PROFILER_BUILTIN_END + + /** Profiler start point function with custom tag */ + #define LV_PROFILER_BEGIN_TAG LV_PROFILER_BUILTIN_BEGIN_TAG + + /** Profiler end point function with custom tag */ + #define LV_PROFILER_END_TAG LV_PROFILER_BUILTIN_END_TAG + + /*Enable layout profiler*/ + #define LV_PROFILER_LAYOUT 1 + + /*Enable disp refr profiler*/ + #define LV_PROFILER_REFR 1 + + /*Enable draw profiler*/ + #define LV_PROFILER_DRAW 1 + + /*Enable indev profiler*/ + #define LV_PROFILER_INDEV 1 + + /*Enable decoder profiler*/ + #define LV_PROFILER_DECODER 1 + + /*Enable font profiler*/ + #define LV_PROFILER_FONT 1 + + /*Enable fs profiler*/ + #define LV_PROFILER_FS 1 + + /*Enable style profiler*/ + #define LV_PROFILER_STYLE 0 + + /*Enable timer profiler*/ + #define LV_PROFILER_TIMER 1 + + /*Enable cache profiler*/ + #define LV_PROFILER_CACHE 1 + + /*Enable event profiler*/ + #define LV_PROFILER_EVENT 1 +#endif + +/** 1: Enable Monkey test */ +#define LV_USE_MONKEY 0 + +/** 1: Enable grid navigation */ +#define LV_USE_GRIDNAV 0 + +/** 1: Enable `lv_obj` fragment logic */ +#define LV_USE_FRAGMENT 0 + +/** 1: Support using images as font in label or span widgets */ +#define LV_USE_IMGFONT 1 + +/** 1: Enable an observer pattern implementation */ +#define LV_USE_OBSERVER 1 + +/** 1: Enable Pinyin input method + * - Requires: lv_keyboard */ +#define LV_USE_IME_PINYIN 0 +#if LV_USE_IME_PINYIN + /** 1: Use default thesaurus. + * @note If you do not use the default thesaurus, be sure to use `lv_ime_pinyin` after setting the thesaurus. */ + #define LV_IME_PINYIN_USE_DEFAULT_DICT 1 + /** Set maximum number of candidate panels that can be displayed. + * @note This needs to be adjusted according to size of screen. */ + #define LV_IME_PINYIN_CAND_TEXT_NUM 6 + + /** Use 9-key input (k9). */ + #define LV_IME_PINYIN_USE_K9_MODE 1 + #if LV_IME_PINYIN_USE_K9_MODE == 1 + #define LV_IME_PINYIN_K9_CAND_TEXT_NUM 3 + #endif /*LV_IME_PINYIN_USE_K9_MODE*/ +#endif + +/** 1: Enable file explorer. + * - Requires: lv_table */ +#define LV_USE_FILE_EXPLORER 0 +#if LV_USE_FILE_EXPLORER + /** Maximum length of path */ + #define LV_FILE_EXPLORER_PATH_MAX_LEN (128) + /** Quick access bar, 1:use, 0:do not use. + * - Requires: lv_list */ + #define LV_FILE_EXPLORER_QUICK_ACCESS 1 +#endif + +/** 1: Enable Font manager */ +/* #define LV_USE_FONT_MANAGER 0 */ +/* #if LV_USE_FONT_MANAGER */ + +/**Font manager name max length*/ +/* #define LV_FONT_MANAGER_NAME_MAX_LEN 32 */ + +/* #endif */ + +/** Enable emulated input devices, time emulation, and screenshot compares. */ +#define LV_USE_TEST 0 +#if LV_USE_TEST + +/** Enable `lv_test_screenshot_compare`. + * Requires libpng and a few MB of extra RAM. */ +#define LV_USE_TEST_SCREENSHOT_COMPARE 0 +#endif /*LV_USE_TEST*/ + +/** Enable loading XML UIs runtime */ +#define LV_USE_XML 0 + +/*1: Enable color filter style*/ +/* #define LV_USE_COLOR_FILTER 0 */ + +/*================== + * DEVICES + *==================*/ + +/** Use SDL to open window on PC and handle mouse and keyboard. */ + +#ifdef MICROPY_SDL + #define LV_USE_SDL MICROPY_SDL +#else + #define LV_USE_SDL 0 +#endif +#if LV_USE_SDL + #define LV_SDL_INCLUDE_PATH + #define LV_SDL_RENDER_MODE LV_DISPLAY_RENDER_MODE_DIRECT /**< LV_DISPLAY_RENDER_MODE_DIRECT is recommended for best performance */ + #define LV_SDL_BUF_COUNT 1 /**< 1 or 2 */ + #define LV_SDL_ACCELERATED 1 +/**< 1: Use hardware acceleration*/ + #define LV_SDL_FULLSCREEN 0 /**< 1: Make the window full screen by default */ + #define LV_SDL_DIRECT_EXIT 1 /**< 1: Exit the application when all SDL windows are closed */ + #define LV_SDL_MOUSEWHEEL_MODE LV_SDL_MOUSEWHEEL_MODE_ENCODER /*LV_SDL_MOUSEWHEEL_MODE_ENCODER/CROWN*/ +#endif + +/** Use X11 to open window on Linux desktop and handle mouse and keyboard */ +#define LV_USE_X11 0 +#if LV_USE_X11 + #define LV_X11_DIRECT_EXIT 1 /**< Exit application when all X11 windows have been closed */ + #define LV_X11_DOUBLE_BUFFER 1 /**< Use double buffers for rendering */ + /* Select only 1 of the following render modes (LV_X11_RENDER_MODE_PARTIAL preferred!). */ + #define LV_X11_RENDER_MODE_PARTIAL 1 /**< Partial render mode (preferred) */ + #define LV_X11_RENDER_MODE_DIRECT 0 /**< Direct render mode */ + #define LV_X11_RENDER_MODE_FULL 0 /**< Full render mode */ +#endif + +/** Use Wayland to open a window and handle input on Linux or BSD desktops */ +#define LV_USE_WAYLAND 0 +#if LV_USE_WAYLAND + #define LV_WAYLAND_WINDOW_DECORATIONS 0 /**< Draw client side window decorations only necessary on Mutter/GNOME */ + #define LV_WAYLAND_WL_SHELL 0 /**< Use the legacy wl_shell protocol instead of the default XDG shell */ +#endif + +/** Driver for /dev/fb */ + +#ifdef MICROPY_FB + #define LV_USE_LINUX_FBDEV MICROPY_FB +#else + #define LV_USE_LINUX_FBDEV 0 +#endif +#if LV_USE_LINUX_FBDEV + #define LV_LINUX_FBDEV_BSD 0 + #define LV_LINUX_FBDEV_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL + #define LV_LINUX_FBDEV_BUFFER_COUNT 0 + #define LV_LINUX_FBDEV_BUFFER_SIZE 60 + #define LV_LINUX_FBDEV_MMAP 1 +#endif + +/** Use Nuttx to open window and handle touchscreen */ +#define LV_USE_NUTTX 0 + +#if LV_USE_NUTTX + #define LV_USE_NUTTX_INDEPENDENT_IMAGE_HEAP 0 + + /** Use independent image heap for default draw buffer */ + #define LV_NUTTX_DEFAULT_DRAW_BUF_USE_INDEPENDENT_IMAGE_HEAP 0 + + #define LV_USE_NUTTX_LIBUV 0 + + /** Use Nuttx custom init API to open window and handle touchscreen */ + #define LV_USE_NUTTX_CUSTOM_INIT 0 + + /** Driver for /dev/lcd */ + #define LV_USE_NUTTX_LCD 0 + #if LV_USE_NUTTX_LCD + #define LV_NUTTX_LCD_BUFFER_COUNT 0 + #define LV_NUTTX_LCD_BUFFER_SIZE 60 + #endif + + /** Driver for /dev/input */ + #define LV_USE_NUTTX_TOUCHSCREEN 0 + + /*Touchscreen cursor size in pixels(<=0: disable cursor)*/ + #define LV_NUTTX_TOUCHSCREEN_CURSOR_SIZE 0 +#endif + +/** Driver for /dev/dri/card */ +#define LV_USE_LINUX_DRM 0 + +#if LV_USE_LINUX_DRM + + /* Use the MESA GBM library to allocate DMA buffers that can be + * shared across sub-systems and libraries using the Linux DMA-BUF API. + * The GBM library aims to provide a platform independent memory management system + * it supports the major GPU vendors - This option requires linking with libgbm */ + #define LV_LINUX_DRM_GBM_BUFFERS 0 +#endif + +/** Interface for TFT_eSPI */ +#define LV_USE_TFT_ESPI 0 + +/** Driver for evdev input devices */ +#define LV_USE_EVDEV 0 + +/** Driver for libinput input devices */ +#define LV_USE_LIBINPUT 0 + +#if LV_USE_LIBINPUT + #define LV_LIBINPUT_BSD 0 + + /** Full keyboard support */ + #define LV_LIBINPUT_XKB 0 + #if LV_LIBINPUT_XKB + /** "setxkbmap -query" can help find the right values for your keyboard */ + #define LV_LIBINPUT_XKB_KEY_MAP { .rules = NULL, .model = "pc101", .layout = "us", .variant = NULL, .options = NULL } + #endif +#endif + +/* Drivers for LCD devices connected via SPI/parallel port */ +#define LV_USE_ST7735 0 +#define LV_USE_ST7789 0 +#define LV_USE_ST7796 0 +#define LV_USE_ILI9341 0 +#define LV_USE_FT81X 0 + +#if (LV_USE_ST7735 | LV_USE_ST7789 | LV_USE_ST7796 | LV_USE_ILI9341) + #define LV_USE_GENERIC_MIPI 1 +#else + #define LV_USE_GENERIC_MIPI 0 +#endif + +/** Driver for Renesas GLCD */ +#define LV_USE_RENESAS_GLCDC 0 + +/** Driver for ST LTDC */ +#define LV_USE_ST_LTDC 0 +#if LV_USE_ST_LTDC + /* Only used for partial. */ + #define LV_ST_LTDC_USE_DMA2D_FLUSH 0 +#endif + +/** LVGL Windows backend */ +#define LV_USE_WINDOWS 0 + +/** LVGL UEFI backend */ +#define LV_USE_UEFI 0 +#if LV_USE_UEFI + #define LV_USE_UEFI_INCLUDE "myefi.h" /**< Header that hides the actual framework (EDK2, gnu-efi, ...) */ + #define LV_UEFI_USE_MEMORY_SERVICES 0 /**< Use the memory functions from the boot services table */ +#endif + +/** Use OpenGL to open window on PC and handle mouse and keyboard */ +#define LV_USE_OPENGLES 0 +#if LV_USE_OPENGLES + #define LV_USE_OPENGLES_DEBUG 1 /**< Enable or disable debug for opengles */ +#endif + +/** QNX Screen display and input drivers */ +#define LV_USE_QNX 0 +#if LV_USE_QNX + #define LV_QNX_BUF_COUNT 1 /**< 1 or 2 */ +#endif + +/*================== +* EXAMPLES +*==================*/ + +/** Enable examples to be built with the library. */ +#define LV_BUILD_EXAMPLES 0 + +/*=================== + * DEMO USAGE + ====================*/ + +/** Show some widgets. This might be required to increase `LV_MEM_SIZE`. */ +#define LV_USE_DEMO_WIDGETS 0 + +/** Demonstrate usage of encoder and keyboard. */ +#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0 + +/** Benchmark your system */ +#define LV_USE_DEMO_BENCHMARK 0 + +/** Render test for each primitive. + * - Requires at least 480x272 display. */ +#define LV_USE_DEMO_RENDER 0 + +/** Stress test for LVGL */ +#define LV_USE_DEMO_STRESS 0 + +/** Music player demo */ +#define LV_USE_DEMO_MUSIC 0 +#if LV_USE_DEMO_MUSIC + #define LV_DEMO_MUSIC_SQUARE 0 + #define LV_DEMO_MUSIC_LANDSCAPE 0 + #define LV_DEMO_MUSIC_ROUND 0 + #define LV_DEMO_MUSIC_LARGE 0 + #define LV_DEMO_MUSIC_AUTO_PLAY 0 +#endif + +/** Vector graphic demo */ +#define LV_USE_DEMO_VECTOR_GRAPHIC 0 + +/*--------------------------- + * Demos from lvgl/lv_demos + ---------------------------*/ + +/** Flex layout demo */ +#define LV_USE_DEMO_FLEX_LAYOUT 0 + +/** Smart-phone like multi-language demo */ +#define LV_USE_DEMO_MULTILANG 0 + +/** Widget transformation demo */ +#define LV_USE_DEMO_TRANSFORM 0 + +/** Demonstrate scroll settings */ +#define LV_USE_DEMO_SCROLL 0 + +/*E-bike demo with Lottie animations (if LV_USE_LOTTIE is enabled)*/ +#define LV_USE_DEMO_EBIKE 0 +#if LV_USE_DEMO_EBIKE + #define LV_DEMO_EBIKE_PORTRAIT 0 /*0: for 480x270..480x320, 1: for 480x800..720x1280*/ +#endif + +/** High-resolution demo */ +#define LV_USE_DEMO_HIGH_RES 0 + +/* Smart watch demo */ +#define LV_USE_DEMO_SMARTWATCH 0 + +/*--END OF LV_CONF_H--*/ + +#endif /*LV_CONF_H*/ + +#endif /*End of "Content enable"*/ diff --git a/driver/stm32/st_ltdc/manifest.py b/driver/stm32/st_ltdc/manifest.py new file mode 100644 index 0000000000..126ca40bdd --- /dev/null +++ b/driver/stm32/st_ltdc/manifest.py @@ -0,0 +1,7 @@ +module("lv_utils.py", base_path="../../../lib") +module("st_ltdc_utils.py", base_path=".") +module("display_driver.py", base_path=".") +module("display_conf.py", base_path=".") +module("panels.py", base_path=".") +module("boards.py", base_path=".") +module("board_config.py", base_path=".") diff --git a/driver/stm32/st_ltdc/modules/micropython.mk b/driver/stm32/st_ltdc/modules/micropython.mk new file mode 100644 index 0000000000..5e68ea8c31 --- /dev/null +++ b/driver/stm32/st_ltdc/modules/micropython.mk @@ -0,0 +1,14 @@ +################################################################################ + +ST_LTDC_MOD_DIR := $(USERMOD_DIR) +MOD_ROOT_DIR := ${TOP_DIR}/user_modules/lv_binding_micropython +CFLAGS_USERMOD += -I$(MOD_ROOT_DIR)/lvgl/src -I$(MOD_ROOT_DIR)/lvgl/src/display -I$(MOD_ROOT_DIR)/lvgl/src/core -I$(MOD_ROOT_DIR)/driver/include +CFLAGS_USERMOD += -I$(ST_LTDC_MOD_DIR) +SRC_ST_LTDC_C := $(shell find $(ST_LTDC_MOD_DIR) -type f -name "*.c") +SRC_USERMOD_C += $(SRC_ST_LTDC_C) + +BSP_LTDC_MOD_DIR := $(USERMOD_DIR)/../../$(BOARD) +SRC_BSP_LTDC_C := $(shell find $(BSP_LTDC_MOD_DIR) -type f -name "*.c") +SRC_USERMOD_C += $(SRC_BSP_LTDC_C) +$(shell printf 'BOARD = "%s"\nVERSION = "%s"\n' "$(BOARD)" "$(BOARD_VARIANT)" > $(USERMOD_DIR)/../board_config.py) +$(info user_modules=st_ltdc, $(BOARD) $(BOARD_VARIANT)) \ No newline at end of file diff --git a/driver/stm32/st_ltdc/modules/st_ltdc.c b/driver/stm32/st_ltdc/modules/st_ltdc.c new file mode 100644 index 0000000000..a4b5a40b6b --- /dev/null +++ b/driver/stm32/st_ltdc/modules/st_ltdc.c @@ -0,0 +1,629 @@ +// LTDC display driver for STM32H7 with LVGL. +// Provides MicroPython interface for configuring and using the LTDC peripheral. + +#include +#include +#include +#include + +#include "mpconfigboard.h" +#include "lv_conf.h" +#include "py/mphal.h" +#include "py/gc.h" +#include "py/runtime.h" +#include "py/nlr.h" +#include "boardctrl.h" +#include +#include "mpu.h" +#include "led.h" +#include +#include +#include +// Required by common.h which uses the STATIC macro +#define STATIC static +#include + +#define LTDC_POL_HS_MASK (1 << 0) // bit 0: HSYNC polarity +#define LTDC_POL_VS_MASK (1 << 1) // bit 1: VSYNC polarity +#define LTDC_POL_DE_MASK (1 << 2) // bit 2: DE polarity +#define LTDC_POL_PC_MASK (1 << 3) // bit 3: Pixel Clock polarity + +// Global because the HAL LTDC IRQ handler references it by name +LTDC_HandleTypeDef hltdc = {0}; +DMA2D_HandleTypeDef hdma2d = {0}; + +// LTDC display object - holds timing parameters, framebuffer pointers +// and LVGL display state for the STM32H7 LTDC peripheral. +typedef struct _ltdc_display_obj_t { + mp_obj_base_t base; + uint32_t hsync; // horizontal sync width + uint32_t vsync; // vertical sync height + uint32_t hbp; // horizontal back porch + uint32_t vbp; // vertical back porch + uint32_t hfp; // horizontal front porch + uint32_t vfp; // vertical front porch + uint32_t width; // active display width + uint32_t height; // active display height + uint32_t polarity; // sync polarity flags + uint32_t fb_size; // framebuffer size in bytes + uint32_t fb_addr; // framebuffer base address + mp_hal_pin_obj_t rst_pin; // LCD reset pin + mp_hal_pin_obj_t bl_pin; // LCD backlight pin + pyb_led_t bl_led; // LCD backlight LED index + lv_display_t *disp; // LVGL display structure + lv_color_format_t color_format; // LVGL color format + lv_color_t *fb[2]; // LTDC framebuffer pointers +} ltdc_display_obj_t; + +MP_REGISTER_ROOT_POINTER(void *ltdc_fb[2]); +MP_REGISTER_ROOT_POINTER(mp_obj_t current_ltdc_obj); + +void led_set_intensity(pyb_led_t led, mp_int_t intensity); +static void lcd_hw_reset(ltdc_display_obj_t *self); +static void optimize_axi_for_display(void); +static void configure_fb_mpu_wt(ltdc_display_obj_t *ltdc_obj); +static bool config_ltdc(ltdc_display_obj_t *self); +static bool config_dma2d(ltdc_display_obj_t *self); +static mp_obj_t mp_ltdc_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +static mp_obj_t mp_ltdc_deinit(mp_obj_t self_in); +static mp_obj_t mp_ltdc_framebuffer(mp_obj_t self_in, mp_obj_t n_obj); +static mp_obj_t mp_ltdc_make_new(const mp_obj_type_t *type, + size_t n_args, size_t n_kw, + const mp_obj_t *all_args); +static void mp_ltdc_flush(lv_display_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); + +static MP_DEFINE_CONST_FUN_OBJ_KW(mp_ltdc_init_obj, 1, mp_ltdc_init); +static MP_DEFINE_CONST_FUN_OBJ_1(mp_ltdc_deinit_obj, mp_ltdc_deinit); +static MP_DEFINE_CONST_FUN_OBJ_2(mp_ltdc_framebuffer_obj, mp_ltdc_framebuffer); +DEFINE_PTR_OBJ(mp_ltdc_flush); + +static const mp_rom_map_elem_t mp_ltdc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_st_ltdc) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&mp_ltdc_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&mp_ltdc_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&PTR_OBJ(mp_ltdc_flush))}, + { MP_ROM_QSTR(MP_QSTR_framebuffer), MP_ROM_PTR(&mp_ltdc_framebuffer_obj)}, +}; + +static MP_DEFINE_CONST_DICT(mp_ltdc_locals_dict, mp_ltdc_locals_dict_table); + +// Display type definition using slot-based system +MP_DEFINE_CONST_OBJ_TYPE( + mp_ltdc_type, + MP_QSTR_LTDCDisplay, + MP_TYPE_FLAG_NONE, + make_new, mp_ltdc_make_new, + locals_dict, &mp_ltdc_locals_dict +); + +// Module globals table +static const mp_rom_map_elem_t mod_ltdc_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_st_ltdc) }, + { MP_ROM_QSTR(MP_QSTR_LTDC), MP_ROM_PTR(&mp_ltdc_type) }, + // Polarity constants + { MP_ROM_QSTR(MP_QSTR_POL_HS), MP_ROM_INT(LTDC_POL_HS_MASK) }, + { MP_ROM_QSTR(MP_QSTR_POL_VS), MP_ROM_INT(LTDC_POL_VS_MASK) }, + { MP_ROM_QSTR(MP_QSTR_POL_DE), MP_ROM_INT(LTDC_POL_DE_MASK) }, + { MP_ROM_QSTR(MP_QSTR_POL_PC), MP_ROM_INT(LTDC_POL_PC_MASK) }, +}; +static MP_DEFINE_CONST_DICT(mod_ltdc_globals, mod_ltdc_globals_table); + +const mp_obj_module_t mp_st_ltdc = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mod_ltdc_globals +}; + +#if MODULE_ST_LTDC_ENABLED +MP_REGISTER_MODULE(MP_QSTR_st_ltdc, mp_st_ltdc); +#endif + +// Configure SDRAM as Write-Through for the framebuffer region. +// Uses Region 6 (MPU_REGION_DMA_UNCACHED_1) which has HIGHER priority +// than Region 5 (sdram.c configures all SDRAM as Write-Back on Region 5). +// ARM MPU rule: higher region number wins on overlap. +// +// Write-Through (TEX=0, C=1, B=0): CPU writes go to BOTH DCache and SDRAM +// simultaneously, so LTDC/DMA2D always read current data from SDRAM +// without needing manual SCB_CleanDCache_by_Addr() calls. +// +// The region size is computed dynamically from the actual framebuffer +// addresses so that only the minimum power-of-2 area is Write-Through +// (4MB for RGB565, 8MB for RGB888 on a 1024x600 display). +#define MPU_CONFIG_WRITE_THROUGH(size) ( \ + MPU_INSTRUCTION_ACCESS_DISABLE << MPU_RASR_XN_Pos \ + | MPU_REGION_FULL_ACCESS << MPU_RASR_AP_Pos \ + | MPU_TEX_LEVEL0 << MPU_RASR_TEX_Pos \ + | MPU_ACCESS_NOT_SHAREABLE << MPU_RASR_S_Pos \ + | MPU_ACCESS_CACHEABLE << MPU_RASR_C_Pos \ + | MPU_ACCESS_NOT_BUFFERABLE << MPU_RASR_B_Pos \ + | 0x00 << MPU_RASR_SRD_Pos \ + | (size) << MPU_RASR_SIZE_Pos \ + | MPU_REGION_ENABLE << MPU_RASR_ENABLE_Pos \ + ) + +// Return the smallest MPU region size (log2 encoding) that covers all +// framebuffers. MPU regions must be power-of-2 sized, minimum 4MB. +static uint32_t fb_mpu_region_size(ltdc_display_obj_t *ltdc_obj) { + uint32_t fb_end = (uint32_t)ltdc_obj->fb[0] + ltdc_obj->fb_size; + if (ltdc_obj->fb[1] != NULL) { + uint32_t end1 = (uint32_t)ltdc_obj->fb[1] + ltdc_obj->fb_size; + if (end1 > fb_end) { + fb_end = end1; + } + } + uint32_t needed = fb_end - ltdc_obj->fb_addr; + + // MPU_REGION_SIZE_xMB = log2(bytes) - 1 + // 4MB = 0x15, 8MB = 0x16, 16MB = 0x17, 32MB = 0x18 + if (needed <= 4 * 1024 * 1024) { + return MPU_REGION_SIZE_4MB; + } + if (needed <= 8 * 1024 * 1024) { + return MPU_REGION_SIZE_8MB; + } + if (needed <= 16 * 1024 * 1024) { + return MPU_REGION_SIZE_16MB; + } + return MPU_REGION_SIZE_32MB; +} + +static void configure_fb_mpu_wt(ltdc_display_obj_t *ltdc_obj) { + uint32_t region_size = fb_mpu_region_size(ltdc_obj); + uint32_t irq_state = mpu_config_start(); + mpu_config_region(MPU_REGION_DMA_UNCACHED_1, // Region 6 > Region 5 + ltdc_obj->fb_addr, + MPU_CONFIG_WRITE_THROUGH(region_size)); + mpu_config_end(irq_state); +} + +// Optimize AXI bus priorities for tear-free LTDC display. +// Gives LTDC top read/write QoS and limits SDRAM to one outstanding +// transaction so the display controller is never starved. +static void optimize_axi_for_display(void) { + // Limit SDRAM to 1 outstanding transaction (Target/Slave Port 5) + GPV->AXI_TARG5_FN_MOD_ISS_BM = 0x01; + // Top priority for LTDC (Initiator/Master Port 6) + GPV->AXI_INI6_READ_QOS = 0x0F; + GPV->AXI_INI6_WRITE_QOS = 0x0F; + // Top priority for DMA2D (Initiator/Master Port 5) + GPV->AXI_INI5_READ_QOS = 0x0E; + GPV->AXI_INI5_WRITE_QOS = 0x0E; + // Low priority for CPU (Initiator/Master Port 2) + GPV->AXI_INI2_READ_QOS = 0x0; + GPV->AXI_INI2_WRITE_QOS = 0x0; +} + +// Return a bytearray view of the requested framebuffer. +// On first call the framebuffer is allocated in SDRAM. +static mp_obj_t mp_ltdc_framebuffer(mp_obj_t self_in, mp_obj_t n_obj) { + int n = mp_obj_get_int(n_obj) - 1; + ltdc_display_obj_t *ltdc_obj = MP_OBJ_TO_PTR(self_in); + + if (n < 0 || n > 1) { + mp_raise_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("framebuffer index %d out of range (1-2)"), n + 1); + } + uint8_t bpp = lv_color_format_get_size(ltdc_obj->color_format); + if (bpp < 2 || bpp > 4) { + mp_raise_msg_varg(&mp_type_ValueError, + MP_ERROR_TEXT("BPP %d out of range (2-3)"), bpp); + } + ltdc_obj->fb_size = ltdc_obj->width * ltdc_obj->height * bpp; + + if (ltdc_obj->fb[n] == NULL) { + // Allocate on extRAM with 1KB alignment for LTDC burst access on AHB + ltdc_obj->fb[n] = MP_STATE_PORT(ltdc_fb[n]) = m_malloc(ltdc_obj->fb_size + 1024); + ltdc_obj->fb[n] = (lv_color_t *)((uint32_t)ltdc_obj->fb[n] + 1024 - (uint32_t)ltdc_obj->fb[n] % 1024); + memset((void *)ltdc_obj->fb[n], 0, ltdc_obj->fb_size); + } + return mp_obj_new_bytearray_by_ref(ltdc_obj->fb_size, (void *)ltdc_obj->fb[n]); +} + +// Constructor - creates display object with timing parameters. +static mp_obj_t mp_ltdc_make_new(const mp_obj_type_t *type, + size_t n_args, size_t n_kw, + const mp_obj_t *all_args) { + enum { + ARG_rst, ARG_width, ARG_height, + ARG_hsync, ARG_hbp, ARG_hfp, + ARG_vsync, ARG_vbp, ARG_vfp, + ARG_fb_addr, ARG_bl, ARG_polarity, + }; + + static const mp_arg_t allowed_args[] = { + // Reset pin (optional) + { MP_QSTR_rst, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + // Panel geometry (required) + { MP_QSTR_width, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_height, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + // Timing in pixel clocks / lines (required) + { MP_QSTR_hsync, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_hbp, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_hfp, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_vsync, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_vbp, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_vfp, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + // Framebuffer base address (optional, default 0xC0000000) + { MP_QSTR_fb_addr, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + // Backlight: Pin object or LED index (required) + { MP_QSTR_bl, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, + // LTDC polarity flags (optional, default = all active-low) + { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + + 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); + + // Invalidate immediately so any LTDC/DMA2D IRQ that fires between the + // soft-reset heap free and the upcoming mp_ltdc_init() call gets a safe + // NULL and returns without dereferencing freed memory. + MP_STATE_PORT(current_ltdc_obj) = MP_OBJ_NULL; + + ltdc_display_obj_t *self = m_new_obj(ltdc_display_obj_t); + self->base.type = type; + + // Reset pin (optional) + if (args[ARG_rst].u_obj != MP_OBJ_NULL) { + self->rst_pin = mp_hal_get_pin_obj(args[ARG_rst].u_obj); + } else { + self->rst_pin = MP_OBJ_NULL; + } + + if (args[ARG_fb_addr].u_obj != mp_const_none) { + self->fb_addr = (uint32_t)mp_obj_get_int_truncated(args[ARG_fb_addr].u_obj); + } else { + self->fb_addr = 0xC0000000; + } + + // Panel geometry + self->width = args[ARG_width].u_int; + self->height = args[ARG_height].u_int; + // Timing + self->hsync = args[ARG_hsync].u_int; + self->hbp = args[ARG_hbp].u_int; + self->hfp = args[ARG_hfp].u_int; + self->vsync = args[ARG_vsync].u_int; + self->vbp = args[ARG_vbp].u_int; + self->vfp = args[ARG_vfp].u_int; + self->polarity = args[ARG_polarity].u_int; + + if (mp_obj_is_small_int(args[ARG_bl].u_obj)) { + self->bl_led = mp_obj_get_int(args[ARG_bl].u_obj); + if (self->bl_led > 0) { + led_set_intensity(self->bl_led, 1); + } + } else if (mp_obj_is_type(args[ARG_bl].u_obj, &pin_type)) { + self->bl_pin = mp_hal_get_pin_obj(args[ARG_bl].u_obj); + } else { + mp_raise_TypeError(MP_ERROR_TEXT("Pin or LED object expected")); + } + + self->fb_size = 0; + self->disp = MP_OBJ_NULL; + self->color_format = LV_COLOR_FORMAT_UNKNOWN; + self->fb[0] = NULL; + self->fb[1] = NULL; + return MP_OBJ_FROM_PTR(self); +} + +// LVGL flush callback supporting DIRECT, FULL and PARTIAL render modes. +// +// DIRECT/FULL: LVGL renders the full screen into one of the double buffers. +// We just swap the LTDC read address at VSYNC — no copy needed. +// flush_ready is signalled by HAL_LTDC_ReloadEventCallback. +// +// Python side: disp.set_buffers(fb1, fb2, len(fb1), lv.DISPLAY_RENDER_MODE.DIRECT) +// where fb1/fb2 come from disp.framebuffer(1) / disp.framebuffer(2). +// +// PARTIAL: LVGL renders only dirty rectangles into small, dedicated render +// buffers (NOT the LTDC framebuffers). color_p holds the rendered pixels +// for the rectangle [area], starting at offset 0. DMA2D copies them to the +// correct position inside fb[0] (the framebuffer LTDC reads from). +// flush_ready is signalled by DMA2D_TransferComplete. +// +// IMPORTANT: for PARTIAL mode the render buffers passed to set_buffers() +// must be SEPARATE from the LTDC framebuffers. If you reuse framebuffer(1) +// as a render buffer, LVGL will overwrite its beginning with the dirty-rect +// pixels, corrupting the display content at screen position (0,0). +// +// Python side: disp.set_buffers(bytearray(W*H//10*BPP), bytearray(W*H//10*BPP), +// W*H//10*BPP, lv.DISPLAY_RENDER_MODE.PARTIAL) +static void mp_ltdc_flush(lv_display_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { + if (MP_STATE_PORT(current_ltdc_obj) == MP_OBJ_NULL) { + lv_display_flush_ready(disp_drv); + return; + } + ltdc_display_obj_t *ltdc_obj = MP_OBJ_TO_PTR(MP_STATE_PORT(current_ltdc_obj)); + ltdc_obj->disp = disp_drv; + if (disp_drv->render_mode == LV_DISPLAY_RENDER_MODE_DIRECT || + disp_drv->render_mode == LV_DISPLAY_RENDER_MODE_FULL) { + if (lv_display_is_double_buffered(disp_drv) && lv_display_flush_is_last(disp_drv)) { + HAL_LTDC_SetAddress_NoReload(&hltdc, (uint32_t)color_p, LTDC_LAYER_1); + __HAL_LTDC_ENABLE_IT(&hltdc, LTDC_IT_RR); + HAL_LTDC_Reload(&hltdc, LTDC_RELOAD_VERTICAL_BLANKING); + } else { + lv_display_flush_ready(ltdc_obj->disp); + } + } else { + int32_t width = lv_area_get_width(area); + int32_t height = lv_area_get_height(area); + uint8_t bpp = lv_color_format_get_size(ltdc_obj->color_format); + // Destination: position (area->x1, area->y1) inside the LTDC framebuffer. + // color_p contains width*height pixels compacted from index 0 (LVGL PARTIAL layout). + uint32_t destination_addr = (uint32_t)ltdc_obj->fb[0] + + ((area->y1 * ltdc_obj->width) + area->x1) * bpp; + + // Only the output-offset register needs to change between flush calls + // (it depends on the dirty-rect width). Avoid full HAL_DMA2D_Init to + // preserve the layer config written by config_dma2d(); just update OOR. + hdma2d.Init.OutputOffset = ltdc_obj->width - width; + WRITE_REG(hdma2d.Instance->OOR, hdma2d.Init.OutputOffset); + + HAL_DMA2D_Start_IT(&hdma2d, (uint32_t)color_p, destination_addr, + width, height); + } +} + +// HAL callback after DMA2D transfer completes. +void DMA2D_TransferComplete(DMA2D_HandleTypeDef *hdma2d) { + if (MP_STATE_PORT(current_ltdc_obj) == MP_OBJ_NULL) { + return; + } + ltdc_display_obj_t *ltdc_obj = MP_OBJ_TO_PTR(MP_STATE_PORT(current_ltdc_obj)); + if (ltdc_obj->disp == NULL) { + return; // display freed by lv.deinit() + } + lv_display_flush_ready(ltdc_obj->disp); +} + +// HAL callback after LTDC vertical-blanking reload completes. +// Signals LVGL that the flush is done so the next frame can be rendered. +void HAL_LTDC_ReloadEventCallback(LTDC_HandleTypeDef *hltdc) { + if (MP_STATE_PORT(current_ltdc_obj) == MP_OBJ_NULL) { + return; + } + ltdc_display_obj_t *ltdc_obj = MP_OBJ_TO_PTR(MP_STATE_PORT(current_ltdc_obj)); + if (ltdc_obj->disp == NULL) { + return; // display freed by lv.deinit() + } + lv_display_flush_ready(ltdc_obj->disp); +} + +static void display_delete_cb(lv_event_t *e) { + (void)e; + MP_STATE_PORT(current_ltdc_obj) = MP_OBJ_NULL; +} + +// Initialize the LTDC peripheral, GPIO and framebuffers. +static mp_obj_t mp_ltdc_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { + ARG_SELF, ARG_LV_DISP, + }; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_self, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_disp, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + ltdc_display_obj_t *self = MP_OBJ_TO_PTR(args[ARG_SELF].u_obj); + + if (args[ARG_LV_DISP].u_obj != MP_OBJ_NULL) { + // lv.display_create() returns an mp_lv_struct_t whose get_buffer + // exposes &self->data (a pointer-to-pointer). Use the standard + // buffer protocol to read out the underlying lv_display_t *. + mp_obj_t disp_obj = args[ARG_LV_DISP].u_obj; + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(disp_obj, &bufinfo, MP_BUFFER_READ); + if (bufinfo.len != sizeof(lv_display_t *)) { + mp_raise_TypeError(MP_ERROR_TEXT("disp: expected lv_display_t pointer")); + } + memcpy(&self->disp, bufinfo.buf, sizeof(lv_display_t *)); + } else { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("LVGL Display object is mandatory")); + } + + // Update MP_STATE_PORT(current_ltdc_obj) immediately so any IRQ that fires between now + // and the end of init uses the new (valid) Python object, not a dangling + // pointer left over from a previous soft reset. + MP_STATE_PORT(current_ltdc_obj) = MP_OBJ_FROM_PTR(self); + lv_display_add_event_cb(self->disp, display_delete_cb, LV_EVENT_DELETE, self); + + self->color_format = lv_display_get_color_format(self->disp); + + optimize_axi_for_display(); + + // Allocate both framebuffers before configuring the MPU so the + // Write-Through region can be sized to cover them both. + mp_ltdc_framebuffer(MP_OBJ_FROM_PTR(self), mp_obj_new_int(1)); + mp_ltdc_framebuffer(MP_OBJ_FROM_PTR(self), mp_obj_new_int(2)); + if (self->fb[0] == NULL || self->fb[1] == NULL) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Failed allocating frame buffers")); + } + configure_fb_mpu_wt(self); + if (self->bl_pin != MP_OBJ_NULL) { + mp_hal_pin_high(self->bl_pin); + } else if (self->bl_led > 0) { + led_set_intensity(self->bl_led, 50); + } + lcd_hw_reset(self); + if (!config_ltdc(self)) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("LTDC layer config error")); + } + if (!config_dma2d(self)) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("DMA2D init error")); + } + return mp_const_none; +} + +// Detach from the display: turn off the backlight, blank the layer and +// unregister the current object so IRQ callbacks become no-ops. +static mp_obj_t mp_ltdc_deinit(mp_obj_t self_in) { + ltdc_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + // Clear self->disp BEFORE clearing current_ltdc_obj so the IRQ callbacks + // see disp==NULL and bail safely even if an interrupt fires between the + // two stores (the disp==NULL guard is checked under current_ltdc_obj!=NULL). + self->disp = NULL; + // Now invalidate MP_STATE_PORT(current_ltdc_obj) so IRQ callbacks that fire after this + // point are no-ops. + MP_STATE_PORT(current_ltdc_obj) = MP_OBJ_NULL; + if (self->bl_pin != MP_OBJ_NULL) { + mp_hal_pin_low(self->bl_pin); + } else if (self->bl_led > 0) { + led_set_intensity(self->bl_led, 0); + } + // Blank the layer (alpha=0) so the panel goes dark without stopping clocks. + // Use NoReload + VBL reload to avoid tearing on the final frame. + HAL_LTDC_SetAlpha_NoReload(&hltdc, 0, LTDC_LAYER_1); + HAL_LTDC_Reload(&hltdc, LTDC_RELOAD_VERTICAL_BLANKING); + if (HAL_LTDC_DeInit(&hltdc) != HAL_OK) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("LTDC DeInit error")); + } + return mp_const_none; +} + +// Toggle the LCD reset pin to perform a hardware reset. +static void lcd_hw_reset(ltdc_display_obj_t *self) { + if (self->rst_pin == MP_OBJ_NULL) { + return; + } + mp_hal_pin_low(self->rst_pin); + mp_hal_delay_ms(100); + mp_hal_pin_high(self->rst_pin); + mp_hal_delay_ms(1); +} + +// Configure the LTDC peripheral and Layer 1 with the stored timing parameters. +// This calls HAL_LTDC_Init() which triggers HAL_LTDC_MspInit() (clocks/GPIO/PLL3). +static bool config_ltdc(ltdc_display_obj_t *self) { + LTDC_LayerCfgTypeDef layer_cfg = {0}; + + hltdc.Instance = LTDC; + hltdc.Init.HSPolarity = (self->polarity & LTDC_POL_HS_MASK) + ? LTDC_HSPOLARITY_AH // active high + : LTDC_HSPOLARITY_AL; // active low + hltdc.Init.VSPolarity = (self->polarity & LTDC_POL_VS_MASK) + ? LTDC_VSPOLARITY_AH + : LTDC_VSPOLARITY_AL; + hltdc.Init.DEPolarity = (self->polarity & LTDC_POL_DE_MASK) + ? LTDC_DEPOLARITY_AH + : LTDC_DEPOLARITY_AL; + hltdc.Init.PCPolarity = (self->polarity & LTDC_POL_PC_MASK) + ? LTDC_PCPOLARITY_IPC // inverted + : LTDC_PCPOLARITY_IIPC; // not inverted + // Horizontal timing + hltdc.Init.HorizontalSync = self->hsync - 1; + hltdc.Init.AccumulatedHBP = self->hsync + self->hbp - 1; + hltdc.Init.AccumulatedActiveW = self->hsync + self->hbp + self->width - 1; + hltdc.Init.TotalWidth = self->hsync + self->hbp + self->width + self->hfp - 1; + + // Vertical timing + hltdc.Init.VerticalSync = self->vsync - 1; + hltdc.Init.AccumulatedVBP = self->vsync + self->vbp - 1; + hltdc.Init.AccumulatedActiveH = self->vsync + self->vbp + self->height - 1; + hltdc.Init.TotalHeigh = self->vsync + self->vbp + self->height + self->vfp - 1; + hltdc.Init.Backcolor.Blue = 0; + hltdc.Init.Backcolor.Green = 0; + hltdc.Init.Backcolor.Red = 0; + if (HAL_LTDC_Init(&hltdc) != HAL_OK) { + return false; + } + + if (self->color_format == LV_COLOR_FORMAT_RGB565) { + layer_cfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565; + } else if (self->color_format == LV_COLOR_FORMAT_RGB888) { + layer_cfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB888; + } else if (self->color_format == LV_COLOR_FORMAT_ARGB8888) { + layer_cfg.PixelFormat = LTDC_PIXEL_FORMAT_ARGB8888; + } else { + return false; + } + + layer_cfg.WindowX0 = 0; + layer_cfg.WindowX1 = self->width; + layer_cfg.WindowY0 = 0; + layer_cfg.WindowY1 = self->height; + layer_cfg.FBStartAdress = (uint32_t)self->fb[0]; + layer_cfg.Alpha = 255; + layer_cfg.Alpha0 = 0; + layer_cfg.Backcolor.Blue = 0; + layer_cfg.Backcolor.Green = 0; + layer_cfg.Backcolor.Red = 0; + layer_cfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA; + layer_cfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA; + layer_cfg.ImageWidth = self->width; + layer_cfg.ImageHeight = self->height; + + if (HAL_LTDC_ConfigLayer(&hltdc, &layer_cfg, LTDC_LAYER_1) != HAL_OK) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("LTDC layer config failed")); + return false; + } + + return true; +} + +// Configure the DMA2D peripheral. +static bool config_dma2d(ltdc_display_obj_t *self) { + uint32_t out_color_mode; + uint32_t in_color_mode; + + if (self->color_format == LV_COLOR_FORMAT_RGB565) { + out_color_mode = DMA2D_OUTPUT_RGB565; + in_color_mode = DMA2D_INPUT_RGB565; + } else if (self->color_format == LV_COLOR_FORMAT_RGB888) { + out_color_mode = DMA2D_OUTPUT_RGB888; + in_color_mode = DMA2D_INPUT_RGB888; + } else if (self->color_format == LV_COLOR_FORMAT_ARGB8888) { + out_color_mode = DMA2D_OUTPUT_ARGB8888; + in_color_mode = DMA2D_INPUT_ARGB8888; + } else { + return false; + } + + hdma2d.Instance = DMA2D; + hdma2d.Init.Mode = DMA2D_M2M; + hdma2d.Init.ColorMode = out_color_mode; + hdma2d.Init.OutputOffset = 0x0; + hdma2d.Init.BytesSwap = DMA2D_BYTES_REGULAR; + hdma2d.Init.LineOffsetMode = DMA2D_LOM_PIXELS; + hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA; + hdma2d.LayerCfg[1].InputAlpha = 0x0; + hdma2d.LayerCfg[1].InputColorMode = in_color_mode; + hdma2d.LayerCfg[1].InputOffset = 0x0; + hdma2d.LayerCfg[1].AlphaInverted = DMA2D_REGULAR_ALPHA; + hdma2d.LayerCfg[1].RedBlueSwap = DMA2D_RB_REGULAR; + hdma2d.LayerCfg[1].ChromaSubSampling = DMA2D_NO_CSS; + + hdma2d.XferCpltCallback = DMA2D_TransferComplete; + if (HAL_DMA2D_Init(&hdma2d) != HAL_OK) { + return false; + } + HAL_DMA2D_ConfigLayer(&hdma2d, 1); + return true; +} + +void HAL_DMA2D_MspInit(DMA2D_HandleTypeDef *hdma2d) { + __HAL_RCC_DMA2D_CLK_ENABLE(); + HAL_NVIC_SetPriority(DMA2D_IRQn, 1, 1); + HAL_NVIC_EnableIRQ(DMA2D_IRQn); +} + +void HAL_DMA2D_MspDeInit(DMA2D_HandleTypeDef *hdma2d) { + HAL_NVIC_DisableIRQ(DMA2D_IRQn); + __HAL_RCC_DMA2D_CLK_DISABLE(); +} + +// LTDC interrupt handler, forwarded to the HAL. +void LTDC_IRQHandler(void) { + HAL_LTDC_IRQHandler(&hltdc); +} + +// DMA2D interrupt handler, forwarded to the HAL. +void DMA2D_IRQHandler(void) { + HAL_DMA2D_IRQHandler(&hdma2d); +} diff --git a/driver/stm32/st_ltdc/modules/touch_i2c.c b/driver/stm32/st_ltdc/modules/touch_i2c.c new file mode 100644 index 0000000000..c8557b2f19 --- /dev/null +++ b/driver/stm32/st_ltdc/modules/touch_i2c.c @@ -0,0 +1,291 @@ +// Touch pointer driver for STM32H7 with LVGL. +// Provides MicroPython interface for configuring and using various touch I2C devices. +// Supports both hardware I2C (HAL) and software I2C (bit-bang) modes. + +#include +#include +#include +#include + +#include "mpconfigboard.h" +#include "py/mphal.h" +#include "boardctrl.h" +#include +#include +#include +#include +// Required by common.h which uses the STATIC macro +#define STATIC static +#include + +// GT9XX I2C addresses (7-bit 0x14) +#define TP_DEF_I2C_ADDR (0x14) +#define TP_DEF_TOUCH_MAX (5) + +#define GT9XX_CFG_ADDR 0x8047 +#define GT9XX_READ_ADDR 0x814E +#define GT9XX_ID_ADDR 0x8140 + +MP_REGISTER_ROOT_POINTER(mp_obj_t current_tp_obj); + +// Multi-touch scan result from the touch controller +typedef struct _touch_data_t { + uint8_t flag; + uint8_t num; + uint16_t x[TP_DEF_TOUCH_MAX]; + uint16_t y[TP_DEF_TOUCH_MAX]; +} touch_data_t; + +// MicroPython touch-pointer object +typedef struct _tp_obj_t { + mp_obj_base_t base; + char device[20]; // touch device + uint8_t touch_max; // maximum number of touch points + uint8_t addr; // i2c address + mp_hal_pin_obj_t rst_pin; // reset pin + mp_hal_pin_obj_t int_pin; // interrupt pin (input after tp reset) + mp_obj_t i2c_obj; // mp i2c object + mp_machine_i2c_p_t *i2c_proto; // mp i2c protocol +} tp_obj_t; + +static void mp_tp_read(lv_indev_t *indev_drv, lv_indev_data_t *data); +static bool config_gt9xx(tp_obj_t *self); +static void gt9xx_hw_reset(tp_obj_t *self); +static touch_data_t gt9xx_touch_scan(tp_obj_t *self); + +static mp_obj_t mp_tp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); +DEFINE_PTR_OBJ(mp_tp_read); + +static const mp_rom_map_elem_t mp_tp_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mod_tp) }, + { MP_ROM_QSTR(MP_QSTR_tp_read), MP_ROM_PTR(&PTR_OBJ(mp_tp_read))}, +}; + +static MP_DEFINE_CONST_DICT(mp_tp_locals_dict, mp_tp_locals_dict_table); + +// Touch type definition using slot-based system +MP_DEFINE_CONST_OBJ_TYPE( + mp_tp_type, + MP_QSTR_TP, + MP_TYPE_FLAG_NONE, + make_new, mp_tp_make_new, + locals_dict, &mp_tp_locals_dict +); + +// Module globals table +static const mp_rom_map_elem_t mod_tp_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_mod_tp) }, + { MP_ROM_QSTR(MP_QSTR_TP), MP_ROM_PTR(&mp_tp_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C_ADDR), MP_ROM_INT(TP_DEF_I2C_ADDR) }, +}; +static MP_DEFINE_CONST_DICT(mod_tp_globals, mod_tp_globals_table); + +const mp_obj_module_t mp_touch_i2c = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mod_tp_globals +}; + +#if MODULE_TOUCH_I2C_ENABLED +MP_REGISTER_MODULE(MP_QSTR_touch_i2c, mp_touch_i2c); +#endif + +// Constructor - create a touch-screen object with device and I2C parameters. +static mp_obj_t mp_tp_make_new(const mp_obj_type_t *type, + size_t n_args, size_t n_kw, + const mp_obj_t *all_args) { + enum { + ARG_TP_DEVICE, ARG_I2C_OBJ, ARG_I2C_ADDR, ARG_TOUCH_MAX, ARG_RST_PIN, ARG_INT_PIN, + }; + + static const mp_arg_t allowed_args[] = { + { MP_QSTR_device, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_i2c, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_addr, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = TP_DEF_I2C_ADDR} }, + { MP_QSTR_tp_max, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = TP_DEF_TOUCH_MAX} }, + { MP_QSTR_tp_rst, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_tp_int, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + + 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); + + tp_obj_t *self = m_new_obj(tp_obj_t); + self->base.type = type; + + if (args[ARG_TP_DEVICE].u_obj != MP_OBJ_NULL) { + const char *s = mp_obj_str_get_str(args[ARG_TP_DEVICE].u_obj); + strncpy(self->device, s, sizeof(self->device) - 1); + self->device[sizeof(self->device) - 1] = '\0'; // Safety null-terminator + } else { + memset(self->device, 0, sizeof(self->device)); + } + self->i2c_obj = args[ARG_I2C_OBJ].u_obj; + self->addr = args[ARG_I2C_ADDR].u_int; + self->touch_max = args[ARG_TOUCH_MAX].u_int; + + if (self->touch_max > TP_DEF_TOUCH_MAX) { + self->touch_max = TP_DEF_TOUCH_MAX; + } + // Convert the MicroPython Pin object to a HAL pin + if (args[ARG_RST_PIN].u_obj != MP_OBJ_NULL) { + self->rst_pin = mp_hal_get_pin_obj(args[ARG_RST_PIN].u_obj); + } + if (args[ARG_INT_PIN].u_obj != MP_OBJ_NULL) { + self->int_pin = mp_hal_get_pin_obj(args[ARG_INT_PIN].u_obj); + } + if (self->i2c_obj == MP_OBJ_NULL) { + mp_raise_TypeError(MP_ERROR_TEXT("I2C object required")); + } + // Get the I2C protocol + self->i2c_proto = (mp_machine_i2c_p_t *) + MP_OBJ_TYPE_GET_SLOT(mp_obj_get_type(self->i2c_obj), protocol); + + if (self->i2c_proto == NULL) { + mp_raise_TypeError(MP_ERROR_TEXT("Object does not support I2C protocol")); + } + if (strcmp(self->device, "GT911") == 0) { + if (!config_gt9xx(self)) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Touch device init error")); + } + } else { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Device not supported")); + } + MP_STATE_PORT(current_tp_obj) = MP_OBJ_FROM_PTR(self); + return MP_STATE_PORT(current_tp_obj); +} + +// LVGL input-device read callback. +// Polls the touch controller and fills data with the current state. +static void mp_tp_read(lv_indev_t *indev_drv, lv_indev_data_t *data) { + static int32_t last_x = 0; + static int32_t last_y = 0; + + if (MP_STATE_PORT(current_tp_obj) == MP_OBJ_NULL) { + data->point.x = last_x; + data->point.y = last_y; + data->state = LV_INDEV_STATE_RELEASED; + return; + } + + tp_obj_t *self = MP_OBJ_TO_PTR(MP_STATE_PORT(current_tp_obj)); + touch_data_t ts_state = gt9xx_touch_scan(self); + + if (ts_state.flag) { + data->point.x = last_x = ts_state.x[0]; + data->point.y = last_y = ts_state.y[0]; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->point.x = last_x; + data->point.y = last_y; + data->state = LV_INDEV_STATE_RELEASED; + } +} + +// Reset the GT9XX touch controller and select I2C address 0x14. +// The GT911 latches its I2C slave address from the INT pin level on the +// rising edge of RST. INT HIGH selects 0x14 (8-bit 0x28/0x29). +static void gt9xx_hw_reset(tp_obj_t *self) { + // Configure INT pin as output and set HIGH for address 0x14 + mp_hal_pin_output(self->rst_pin); + mp_hal_pin_output(self->int_pin); + mp_hal_delay_ms(1); + mp_hal_pin_low(self->rst_pin); + mp_hal_delay_ms(1); + mp_hal_pin_high(self->int_pin); + // Assert reset + mp_hal_delay_ms(10); + // Release reset - GT911 samples INT here for address selection + // INT HIGH on rising RST edge selects address 0x14 (0x28/0x29) + mp_hal_pin_high(self->rst_pin); + mp_hal_delay_ms(60); + // Release INT so the controller can use it for interrupt signaling + mp_hal_pin_input(self->int_pin); + mp_hal_delay_ms(50); +} + +static uint8_t gt9xx_write_data(tp_obj_t *self, uint16_t addr, uint8_t value) { + uint8_t buf[] = { + (uint8_t)(addr >> 8), // Register high byte + (uint8_t)(addr & 0xFF), // Register low byte + value // Data byte to write + }; + + // Prepare the I2C buffer structure + mp_machine_i2c_buf_t i2c_buf = {.len = 3, .buf = buf}; + int ret = self->i2c_proto->transfer((mp_obj_base_t *)self->i2c_obj, self->addr, 1, &i2c_buf, MP_MACHINE_I2C_FLAG_STOP); + + return (ret >= 0) ? 0 : 1; +} + +static uint8_t gt9xx_read_reg(tp_obj_t *self, uint16_t addr, uint8_t cnt, uint8_t *value) { + uint8_t reg_addr_buf[] = { + (uint8_t)(addr >> 8), + (uint8_t)(addr & 0xFF) + }; + + // Write register address (no STOP, uses repeated start) + mp_machine_i2c_buf_t wbuf = {.len = 2, .buf = reg_addr_buf}; + int ret = self->i2c_proto->transfer( + (mp_obj_base_t *)self->i2c_obj, self->addr, 1, &wbuf, 0); + if (ret < 0) { + return 1; + } + + // Read data (with STOP) + mp_machine_i2c_buf_t rbuf = {.len = cnt, .buf = value}; + ret = self->i2c_proto->transfer( + (mp_obj_base_t *)self->i2c_obj, self->addr, 1, &rbuf, + MP_MACHINE_I2C_FLAG_READ | MP_MACHINE_I2C_FLAG_STOP); + + return (ret >= 0) ? 0 : 1; +} + +// Initialize the GT9XX touchscreen controller. +// Reads the product ID (e.g. "911\0" for GT911) and verifies +// that the first character is '9' (GT9xx family). +static bool config_gt9xx(tp_obj_t *self) { + uint8_t gt9xx_info[11] = {0}; + + gt9xx_hw_reset(self); + + if (gt9xx_read_reg(self, GT9XX_ID_ADDR, 11, gt9xx_info) != 0) { + return false; + } + // Verify product ID starts with '9' (GT9xx family: GT911, GT928, etc.) + if (gt9xx_info[0] != '9') { + return false; + } + return true; +} + +// Scan for touch events on the GT9XX controller. +// Register 0x814E bit 7 = buffer ready, bits 3:0 = number of touch points. +// Data is only valid when the buffer-ready flag is set. +static touch_data_t gt9xx_touch_scan(tp_obj_t *self) { + touch_data_t result = {0}; + uint8_t touch_data[2 + 8 * TP_DEF_TOUCH_MAX]; + + if (gt9xx_read_reg(self, GT9XX_READ_ADDR, 2 + 8 * self->touch_max, touch_data) != 0) { + return result; + } + // Always clear the buffer-ready flag so the controller prepares new data + gt9xx_write_data(self, GT9XX_READ_ADDR, 0); + + // Bit 7 must be set for the data to be valid + if (!(touch_data[0] & 0x80)) { + return result; + } + + uint8_t num = touch_data[0] & 0x0f; + if (num >= 1 && num <= self->touch_max) { + result.num = num; + for (uint8_t i = 0; i < num; i++) { + result.x[i] = (touch_data[3 + 8 * i] << 8) | touch_data[2 + 8 * i]; + result.y[i] = (touch_data[5 + 8 * i] << 8) | touch_data[4 + 8 * i]; + } + result.flag = 1; + } + return result; +} diff --git a/driver/stm32/st_ltdc/panels.py b/driver/stm32/st_ltdc/panels.py new file mode 100644 index 0000000000..7e4a88297c --- /dev/null +++ b/driver/stm32/st_ltdc/panels.py @@ -0,0 +1,22 @@ +# panels.py +# Polarity Bitmask: +# [bit 0] HSYNC: 0=Active Low, 1=Active High +# [bit 1] VSYNC: 0=Active Low, 1=Active High +# [bit 2] DE: 0=Active Low, 1=Active High +# [bit 3] PIXCLK: 0=Not Inverted, 1=Inverted + +class IPS1024x600: + WIDTH = 1024 + HEIGHT = 600 + HSYNC, HBP, HFP = 7, 164, 149 + VSYNC, VBP, VFP = 5, 22, 11 + POLARITY = 0 + TOUCH_TYPE = "GT911" + +class RGB043M2: + WIDTH = 800 + HEIGHT = 480 + HSYNC, HBP, HFP = 4, 40, 40 + VSYNC, VBP, VFP = 4, 13, 13 + POLARITY = 8 # Pixel Clock polarity = Inverted + TOUCH_TYPE = "GT911" \ No newline at end of file diff --git a/driver/stm32/st_ltdc/st_ltdc_utils.py b/driver/stm32/st_ltdc/st_ltdc_utils.py new file mode 100644 index 0000000000..298be0001d --- /dev/null +++ b/driver/stm32/st_ltdc/st_ltdc_utils.py @@ -0,0 +1,59 @@ +from machine import I2C, SoftI2C, Pin +import st_ltdc +import touch_i2c +import lv_utils +import lvgl as lv + +def init_display(panel, board, asynchronous=False, exception_sink=None): + """Initialize LTDC + Touch using config panel + board.""" + + if lv.is_initialized(): + lv.deinit() + lv.init() + event_loop = lv_utils.event_loop( + asynchronous=asynchronous, + exception_sink=exception_sink + ) + + # Display + params = { + "width": panel.WIDTH, + "height": panel.HEIGHT, + "hsync": panel.HSYNC, "hbp": panel.HBP, "hfp": panel.HFP, + "vsync": panel.VSYNC, "vbp": panel.VBP, "vfp": panel.VFP, + "fb_addr": getattr(board, 'FB_ADDR', 0xC0000000), + "bl": Pin(board.BL_PIN, Pin.OUT), + "polarity": panel.POLARITY, + } + + if hasattr(board, 'RST_PIN') and board.RST_PIN != "": + params["rst"] = Pin(board.RST_PIN, Pin.OUT) + + disp = st_ltdc.LTDC(**params) + disp_drv = lv.display_create(panel.WIDTH, panel.HEIGHT) + disp_drv.set_color_format(lv.COLOR_FORMAT.RGB565) + disp.init(disp_drv) + buf1 = disp.framebuffer(1) + buf2 = disp.framebuffer(2) + disp_drv.set_buffers(buf1, buf2, len(buf1), lv.DISPLAY_RENDER_MODE.DIRECT) + disp_drv.set_flush_cb(disp.flush) + + # Touch + tp_rst = Pin(board.TP_RST_PIN, Pin.OUT) + tp_int = Pin(board.TP_INT_PIN, Pin.IN) + + if hasattr(board, 'TP_I2C_NUM') and board.TP_I2C_NUM > 0: + tp_i2c = I2C(board.TP_I2C_NUM) + else: + tp_i2c = SoftI2C( + scl=Pin(board.TP_SCL_PIN), + sda=Pin(board.TP_SDA_PIN), + freq=100000 + ) + + touch = touch_i2c.TP(device=panel.TOUCH_TYPE, i2c=tp_i2c, tp_rst=tp_rst, tp_int=tp_int) + indev = lv.indev_create() + indev.set_type(lv.INDEV_TYPE.POINTER) + indev.set_read_cb(touch.tp_read) + + return disp, touch diff --git a/examples/advanced_demo.py b/examples/advanced_demo.py index 2c177806f6..17b368bb58 100644 --- a/examples/advanced_demo.py +++ b/examples/advanced_demo.py @@ -423,6 +423,67 @@ def init_gui_stm32(self): self.indev_drv.set_type(lv.INDEV_TYPE.POINTER) self.indev_drv.set_read_cb(lcd.ts_read) + def init_gui_stm32h7(self): + from machine import I2C, SoftI2C, Pin + from display_conf import board, panel + import st_ltdc + import touch_i2c + import lv_utils + + partial = False + + if lv.is_initialized(): + lv.deinit() + lv.init() + event_loop = lv_utils.event_loop( + asynchronous=False, + exception_sink=None + ) + # --- Display --- + params = { + "width": panel.WIDTH, + "height": panel.HEIGHT, + "hsync": panel.HSYNC, "hbp": panel.HBP, "hfp": panel.HFP, + "vsync": panel.VSYNC, "vbp": panel.VBP, "vfp": panel.VFP, + "fb_addr": getattr(board, 'FB_ADDR', 0xC0000000), + "bl": Pin(board.BL_PIN, Pin.OUT), + "polarity": panel.POLARITY, + } + + if hasattr(board, 'RST_PIN') and board.RST_PIN != "": + params["rst"] = Pin(board.RST_PIN, Pin.OUT) + + disp = st_ltdc.LTDC(**params) + disp_drv = lv.display_create(panel.WIDTH, panel.HEIGHT) + disp_drv.set_color_format(lv.COLOR_FORMAT.RGB888) + disp.init(disp_drv) + if partial: + draw_buf1 = lv.draw_buf_create(panel.WIDTH, 50, lv.COLOR_FORMAT.RGB888, 0) + draw_buf2 = lv.draw_buf_create(panel.WIDTH, 50, lv.COLOR_FORMAT.RGB888, 0) + disp_drv.set_draw_buffers(draw_buf1, draw_buf2) + disp_drv.set_render_mode(lv.DISPLAY_RENDER_MODE.PARTIAL) + else: + buf1 = disp.framebuffer(1) + buf2 = disp.framebuffer(2) + disp_drv.set_buffers(buf1, buf2, len(buf1), lv.DISPLAY_RENDER_MODE.DIRECT) + + disp_drv.set_flush_cb(disp.flush) + + # --- Touch --- + tp_rst = Pin(board.TP_RST_PIN, Pin.OUT) + tp_int = Pin(board.TP_INT_PIN, Pin.IN) + + if hasattr(board, 'TP_I2C_NUM') and board.TP_I2C_NUM > 0: + tp_i2c = I2C(board.TP_I2C_NUM) + else: + tp_i2c = SoftI2C(scl=Pin(board.TP_SCL_PIN), + sda=Pin(board.TP_SDA_PIN), + freq=100000) + touch = touch_i2c.TP(device=panel.TOUCH_TYPE, i2c=tp_i2c, tp_rst=tp_rst, tp_int=tp_int) + indev = lv.indev_create() + indev.set_type(lv.INDEV_TYPE.POINTER) + indev.set_read_cb(touch.tp_read) + def init_gui_rp2(self): import xpt2046 import st77xx @@ -467,6 +528,8 @@ def init_gui(self): self.init_gui_SDL() elif sys.platform == "stm32": self.init_gui_stm32() + elif sys.platform == "pyboard": + self.init_gui_stm32h7() # Create the main screen and load it. From 54f0af96297a51ed3861524224d867d88f411ead Mon Sep 17 00:00:00 2001 From: Nunzio Raciti Date: Thu, 26 Mar 2026 16:34:05 +0100 Subject: [PATCH 2/4] Removed tracking for driver/stm32/st_ltdc/board_config.py --- driver/stm32/st_ltdc/board_config.py | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 driver/stm32/st_ltdc/board_config.py diff --git a/driver/stm32/st_ltdc/board_config.py b/driver/stm32/st_ltdc/board_config.py deleted file mode 100644 index e27a449f46..0000000000 --- a/driver/stm32/st_ltdc/board_config.py +++ /dev/null @@ -1,2 +0,0 @@ -BOARD = "DEV190806042" -VERSION = "" From d31b3e792cabfdb08c1f45d7e51862858a72038f Mon Sep 17 00:00:00 2001 From: Nunzio Raciti Date: Fri, 3 Apr 2026 17:55:43 +0200 Subject: [PATCH 3/4] stm32/st_ltdc: harden init/deinit and validate configuration - Zero-initialize ltdc_display_obj_t to prevent indeterminate bl_pin/bl_led fields from selecting the wrong backlight path - Guard deinit() against out-of-sequence calls on uninitialized handle - Validate LTDC timing parameters (width, height, hsync, vsync > 0) before computing register values to prevent unsigned underflow - Raise ValueError for unsupported BOARD/VERSION/PANEL in display_conf.py instead of leaving variables undefined --- driver/stm32/st_ltdc/display_conf.py | 6 ++++++ driver/stm32/st_ltdc/modules/st_ltdc.c | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/driver/stm32/st_ltdc/display_conf.py b/driver/stm32/st_ltdc/display_conf.py index 9d938ac3dd..3dc370ef8a 100644 --- a/driver/stm32/st_ltdc/display_conf.py +++ b/driver/stm32/st_ltdc/display_conf.py @@ -15,12 +15,18 @@ from boards import STM32H743_CORE_V10 as board elif VERSION == "V13": from boards import STM32H743_CORE_V13 as board + else: + raise ValueError("Unsupported VERSION: " + VERSION) elif BOARD == "FK743M5-XIH6": from boards import FK743M5_XIH6 as board elif BOARD == "DEV190806042": from boards import DEV190806042 as board +else: + raise ValueError("Unsupported BOARD: " + BOARD) if PANEL == "IPS1024x600": from panels import IPS1024x600 as panel elif PANEL == "RGB043M2": from panels import RGB043M2 as panel +else: + raise ValueError("Unsupported PANEL: " + PANEL) diff --git a/driver/stm32/st_ltdc/modules/st_ltdc.c b/driver/stm32/st_ltdc/modules/st_ltdc.c index a4b5a40b6b..778238dbb9 100644 --- a/driver/stm32/st_ltdc/modules/st_ltdc.c +++ b/driver/stm32/st_ltdc/modules/st_ltdc.c @@ -262,6 +262,7 @@ static mp_obj_t mp_ltdc_make_new(const mp_obj_type_t *type, MP_STATE_PORT(current_ltdc_obj) = MP_OBJ_NULL; ltdc_display_obj_t *self = m_new_obj(ltdc_display_obj_t); + memset(self, 0, sizeof(*self)); self->base.type = type; // Reset pin (optional) @@ -465,6 +466,10 @@ static mp_obj_t mp_ltdc_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t * // unregister the current object so IRQ callbacks become no-ops. static mp_obj_t mp_ltdc_deinit(mp_obj_t self_in) { ltdc_display_obj_t *self = MP_OBJ_TO_PTR(self_in); + // Nothing to do if init() was never called or deinit() already ran. + if (MP_STATE_PORT(current_ltdc_obj) == MP_OBJ_NULL) { + return mp_const_none; + } // Clear self->disp BEFORE clearing current_ltdc_obj so the IRQ callbacks // see disp==NULL and bail safely even if an interrupt fires between the // two stores (the disp==NULL guard is checked under current_ltdc_obj!=NULL). @@ -503,6 +508,11 @@ static void lcd_hw_reset(ltdc_display_obj_t *self) { static bool config_ltdc(ltdc_display_obj_t *self) { LTDC_LayerCfgTypeDef layer_cfg = {0}; + if (self->width == 0 || self->height == 0 || + self->hsync == 0 || self->vsync == 0) { + return false; + } + hltdc.Instance = LTDC; hltdc.Init.HSPolarity = (self->polarity & LTDC_POL_HS_MASK) ? LTDC_HSPOLARITY_AH // active high From b6fce8e0d046042168b010c5b30311ddc35c2399 Mon Sep 17 00:00:00 2001 From: Nunzio Raciti Date: Fri, 3 Apr 2026 18:21:01 +0200 Subject: [PATCH 4/4] Update driver/stm32/st_ltdc/modules/st_ltdc.c Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- driver/stm32/st_ltdc/modules/st_ltdc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/stm32/st_ltdc/modules/st_ltdc.c b/driver/stm32/st_ltdc/modules/st_ltdc.c index 778238dbb9..05fd301830 100644 --- a/driver/stm32/st_ltdc/modules/st_ltdc.c +++ b/driver/stm32/st_ltdc/modules/st_ltdc.c @@ -467,7 +467,7 @@ static mp_obj_t mp_ltdc_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t * static mp_obj_t mp_ltdc_deinit(mp_obj_t self_in) { ltdc_display_obj_t *self = MP_OBJ_TO_PTR(self_in); // Nothing to do if init() was never called or deinit() already ran. - if (MP_STATE_PORT(current_ltdc_obj) == MP_OBJ_NULL) { + if (MP_STATE_PORT(current_ltdc_obj) == MP_OBJ_NULL && self->disp == NULL) { return mp_const_none; } // Clear self->disp BEFORE clearing current_ltdc_obj so the IRQ callbacks