diff --git a/src/hal/cmsis/src/st/system_stm32fxxx.c b/src/hal/cmsis/src/st/system_stm32fxxx.c index ee962e54d..a19e127e9 100644 --- a/src/hal/cmsis/src/st/system_stm32fxxx.c +++ b/src/hal/cmsis/src/st/system_stm32fxxx.c @@ -376,6 +376,15 @@ void SystemClock_Config(void) PeriphClkInitStruct.Spi45ClockSelection = RCC_SPI45CLKSOURCE_PLL2; #endif + #if defined(DSI) + PeriphClkInitStruct.PeriphClockSelection |= RCC_PERIPHCLK_DSI; + #if defined(OMV_OSC_DSI_CLKSOURCE) + PeriphClkInitStruct.DsiClockSelection = OMV_OSC_DSI_CLKSOURCE; + #else + PeriphClkInitStruct.DsiClockSelection = RCC_DSICLKSOURCE_PHY; + #endif // defined(OMV_OSC_DSI_CLKSOURCE) + #endif // defined(DSI) + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) { // Initialization Error __fatal_error("HAL_RCCEx_PeriphCLKConfig"); diff --git a/src/hal/stm32/h7/Makefile b/src/hal/stm32/h7/Makefile index 7f6adb23a..b5dea05cd 100644 --- a/src/hal/stm32/h7/Makefile +++ b/src/hal/stm32/h7/Makefile @@ -11,6 +11,7 @@ stm32h7xx_hal_adc.c\ stm32h7xx_hal_adc_ex.c\ stm32h7xx_hal_dac.c\ stm32h7xx_hal_dac_ex.c\ +stm32h7xx_hal_dsi.c\ stm32h7xx_hal.c\ stm32h7xx_hal_cortex.c\ stm32h7xx_hal_crc.c\ diff --git a/src/omv/modules/py_display.c b/src/omv/modules/py_display.c index e785c022a..89a15016f 100644 --- a/src/omv/modules/py_display.c +++ b/src/omv/modules/py_display.c @@ -185,6 +185,72 @@ STATIC mp_obj_t py_display_write(uint n_args, const mp_obj_t *args, mp_map_t *kw } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_display_write_obj, 2, py_display_write); +#ifdef OMV_DSI_DISPLAY_CONTROLLER +STATIC mp_obj_t py_display_dsi_write(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_cmd, ARG_args, ARG_dcs }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_cmd, MP_ARG_INT | MP_ARG_REQUIRED }, + { MP_QSTR_args, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_dcs, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + }; + + // Parse args. + py_display_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + if (display_p->dsi_write == NULL) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected a DSI display controller")); + } else if (args[ARG_args].u_obj == mp_const_none) { + display_p->dsi_write(self, args[ARG_cmd].u_int, NULL, 0, args[ARG_dcs].u_bool); + } else if (mp_obj_is_int(args[ARG_args].u_obj)) { + uint8_t arg = mp_obj_get_int(args[ARG_args].u_obj); + display_p->dsi_write(self, args[ARG_cmd].u_int, &arg, 1, args[ARG_dcs].u_bool); + } else { + mp_buffer_info_t rbuf; + mp_get_buffer_raise(args[ARG_args].u_obj, &rbuf, MP_BUFFER_READ); + display_p->dsi_write(self, args[ARG_cmd].u_int, rbuf.buf, rbuf.len, args[ARG_dcs].u_bool); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_display_dsi_write_obj, 1, py_display_dsi_write); + +STATIC mp_obj_t py_display_dsi_read(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_cmd, ARG_len, ARG_args, ARG_dcs }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_cmd, MP_ARG_INT | MP_ARG_REQUIRED }, + { MP_QSTR_len, MP_ARG_INT | MP_ARG_REQUIRED }, + { MP_QSTR_args, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_dcs, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_bool = false } }, + }; + + // Parse args. + py_display_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + py_display_p_t *display_p = (py_display_p_t *) MP_OBJ_TYPE_GET_SLOT(self->base.type, protocol); + mp_obj_array_t *wbuf = MP_OBJ_TO_PTR(mp_obj_new_bytearray_by_ref(args[ARG_len].u_int, + m_new(byte, args[ARG_len].u_int))); + if (display_p->dsi_read == NULL) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected a DSI display controller")); + } else if (args[ARG_args].u_obj == mp_const_none) { + display_p->dsi_read(self, args[ARG_cmd].u_int, NULL, 0, wbuf->items, wbuf->len, args[ARG_dcs].u_bool); + } else if (mp_obj_is_int(args[ARG_args].u_obj)) { + uint8_t arg = mp_obj_get_int(args[ARG_args].u_obj); + display_p->dsi_read(self, args[ARG_cmd].u_int, &arg, 1, wbuf->items, wbuf->len, args[ARG_dcs].u_bool); + } else { + mp_buffer_info_t rbuf; + mp_get_buffer_raise(args[ARG_args].u_obj, &rbuf, MP_BUFFER_READ); + display_p->dsi_read(self, args[ARG_cmd].u_int, rbuf.buf, rbuf.len, + wbuf->items, wbuf->len, args[ARG_dcs].u_bool); + } + return MP_OBJ_FROM_PTR(wbuf); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_display_dsi_read_obj, 1, py_display_dsi_read); +#endif + STATIC const mp_rom_map_elem_t py_display_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_display) }, { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&py_display_deinit_obj) }, @@ -198,6 +264,10 @@ STATIC const mp_rom_map_elem_t py_display_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_display_clear_obj) }, { MP_ROM_QSTR(MP_QSTR_backlight), MP_ROM_PTR(&py_display_backlight_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&py_display_write_obj) }, + #ifdef OMV_DSI_DISPLAY_CONTROLLER + { MP_ROM_QSTR(MP_QSTR_dsi_write), MP_ROM_PTR(&py_display_dsi_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_dsi_read), MP_ROM_PTR(&py_display_dsi_read_obj) }, + #endif }; MP_DEFINE_CONST_DICT(py_display_locals_dict, py_display_locals_dict_table); @@ -242,5 +312,9 @@ const mp_obj_module_t display_module = { .globals = (mp_obj_t) &globals_dict, }; -MP_REGISTER_MODULE(MP_QSTR_display, display_module); +#ifdef MP_REGISTER_EXTENSIBLE_MODULE +MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_display, display_module); +#else +MP_REGISTER_MODULE(MP_QSTR_udisplay, display_module); +#endif #endif // MICROPY_PY_DISPLAY diff --git a/src/omv/modules/py_display.h b/src/omv/modules/py_display.h index e9dca6102..3c8ad45ae 100644 --- a/src/omv/modules/py_display.h +++ b/src/omv/modules/py_display.h @@ -41,6 +41,7 @@ typedef enum { typedef struct _py_display_obj_t { mp_obj_base_t base; + uint32_t vcid; uint32_t width; uint32_t height; uint32_t framesize; @@ -49,6 +50,7 @@ typedef struct _py_display_obj_t { bool bgr; bool byte_swap; bool display_on; + mp_obj_t controller; #if defined(OMV_SPI_DISPLAY_CONTROLLER) omv_spi_t spi_bus; bool spi_tx_running; @@ -68,6 +70,11 @@ typedef struct _py_display_p_t { float x_scale, float y_scale, rectangle_t *roi, int rgb_channel, int alpha, const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint); void (*set_backlight) (py_display_obj_t *self, uint32_t intensity); + #ifdef OMV_DSI_DISPLAY_CONTROLLER + // To be implemented by MIPI DSI controllers. + int (*dsi_write) (py_display_obj_t *self, uint8_t cmd, uint8_t *args, size_t n_args, bool dcs); + int (*dsi_read) (py_display_obj_t *self, uint8_t cmd, uint8_t *args, size_t n_args, uint8_t *buf, size_t len, bool dcs); + #endif } py_display_p_t; extern const mp_obj_type_t py_spi_display_type; diff --git a/src/omv/ports/stm32/modules/py_display.c b/src/omv/ports/stm32/modules/py_display.c index bcbb2bcb2..7b90c6644 100644 --- a/src/omv/ports/stm32/modules/py_display.c +++ b/src/omv/ports/stm32/modules/py_display.c @@ -6,11 +6,13 @@ * * This work is licensed under the MIT license, see the file LICENSE for details. * - * LTDC display Python module. + * LTDC/DSI display Python module. */ #include "omv_boardconfig.h" -#if MICROPY_PY_DISPLAY && defined(OMV_RGB_DISPLAY_CONTROLLER) +#if MICROPY_PY_DISPLAY && \ + (defined(OMV_RGB_DISPLAY_CONTROLLER) || \ + defined(OMV_DSI_DISPLAY_CONTROLLER)) #include "py/obj.h" #include "py/runtime.h" @@ -21,11 +23,15 @@ #include "omv_gpio.h" #include "py_display.h" -#if defined(OMV_RGB_DISPLAY_BL_PIN) +#if defined(OMV_DSI_DISPLAY_BL_PIN) +#define OMV_DISPLAY_BL_PIN OMV_DSI_DISPLAY_BL_PIN +#elif defined(OMV_RGB_DISPLAY_BL_PIN) #define OMV_DISPLAY_BL_PIN OMV_RGB_DISPLAY_BL_PIN #endif -#if defined(OMV_RGB_DISPLAY_DISP_PIN) +#if defined(OMV_DSI_DISPLAY_DISP_PIN) +#define OMV_DISPLAY_DISP_PIN OMV_DSI_DISPLAY_DISP_PIN +#elif defined(OMV_RGB_DISPLAY_DISP_PIN) #define OMV_DISPLAY_DISP_PIN OMV_RGB_DISPLAY_DISP_PIN #endif @@ -46,6 +52,9 @@ typedef struct display_mode { typedef struct _display_state { py_display_obj_t *self; LTDC_HandleTypeDef hltdc; + #ifdef OMV_DSI_DISPLAY_CONTROLLER + DSI_HandleTypeDef hdsi; + #endif #ifdef OMV_DISPLAY_BL_TIM TIM_HandleTypeDef htim; #endif @@ -56,94 +65,94 @@ static display_state_t display; static const display_mode_t display_modes[] = { { // QVGA - .hactive=320, .vactive=240, .pixel_clock=6144, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=1 + .hactive = 320, .vactive = 240, .pixel_clock = 6144, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 1 }, { // TQVGA - .hactive=240, .vactive=320, .pixel_clock=6426, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=1 + .hactive = 240, .vactive = 320, .pixel_clock = 6426, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 1 }, { // FHVGA - .hactive=480, .vactive=272, .pixel_clock=9633, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=1 + .hactive = 480, .vactive = 272, .pixel_clock = 9633, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 1 }, { // FHVGA2 - .hactive=480, .vactive=128, .pixel_clock=4799, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=1 + .hactive = 480, .vactive = 128, .pixel_clock = 4799, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 1 }, { // VGA - .hactive=640, .vactive=480, .pixel_clock=21363, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=1 + .hactive = 640, .vactive = 480, .pixel_clock = 21363, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 1 }, { // THVGA - .hactive=320, .vactive=480, .pixel_clock=11868, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=1 + .hactive = 320, .vactive = 480, .pixel_clock = 11868, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 1 }, { // FWVGA - .hactive=800, .vactive=480, .pixel_clock=26110, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=1 + .hactive = 800, .vactive = 480, .pixel_clock = 26110, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 1 }, { // FWVGA2 - .hactive=800, .vactive=320, .pixel_clock=17670, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=1 + .hactive = 800, .vactive = 320, .pixel_clock = 17670, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 1 }, { // TFWVGA - .hactive=480, .vactive=800, .pixel_clock=27624, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=9 + .hactive = 480, .vactive = 800, .pixel_clock = 27624, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 9 }, { // TFWVGA2 - .hactive=480, .vactive=480, .pixel_clock=16615, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=1 + .hactive = 480, .vactive = 480, .pixel_clock = 16615, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 1 }, { // SVGA - .hactive=800, .vactive=600, .pixel_clock=32597, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=4 + .hactive = 800, .vactive = 600, .pixel_clock = 32597, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 4 }, { // WSVGA - .hactive=1024, .vactive=600, .pixel_clock=40895, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=4 + .hactive = 1024, .vactive = 600, .pixel_clock = 40895, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 4 }, { // XGA - .hactive=1024, .vactive=768, .pixel_clock=52277, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=8 + .hactive = 1024, .vactive = 768, .pixel_clock = 52277, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 8 }, { // SXGA - .hactive=1280, .vactive=1024, .pixel_clock=85920, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=16 + .hactive = 1280, .vactive = 1024, .pixel_clock = 85920, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 16 }, { // SXGA2 - .hactive=1280, .vactive=400, .pixel_clock=33830, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=1 + .hactive = 1280, .vactive = 400, .pixel_clock = 33830, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 1 }, { // UXGA - .hactive=1600, .vactive=1200, .pixel_clock=124364, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=21 + .hactive = 1600, .vactive = 1200, .pixel_clock = 124364, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 21 }, { // HD - .hactive=1280, .vactive=720, .pixel_clock=60405, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=7 + .hactive = 1280, .vactive = 720, .pixel_clock = 60405, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 7 }, { // FHD - .hactive=1920, .vactive=1080, .pixel_clock=133187, - .hsync_len=32, .hback_porch=40, .hfront_porch=8, - .vsync_len=8, .vback_porch=6, .vfront_porch=17 + .hactive = 1920, .vactive = 1080, .pixel_clock = 133187, + .hsync_len = 32, .hback_porch = 40, .hfront_porch = 8, + .vsync_len = 8, .vback_porch = 6, .vfront_porch = 17 }, }; @@ -151,6 +160,12 @@ void LTDC_IRQHandler() { HAL_LTDC_IRQHandler(&display.hltdc); } +#ifdef OMV_DSI_DISPLAY_CONTROLLER +void DSI_IRQHandler(void) { + HAL_DSI_IRQHandler(&display.hdsi); +} +#endif + static void pll_config(int framesize, int refresh) { uint32_t pixel_clock = (display_modes[framesize].pixel_clock * refresh) / 60; @@ -209,6 +224,97 @@ static void pll_config(int framesize, int refresh) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unable to initialize LTDC PLL!")); } +#ifdef OMV_DSI_DISPLAY_CONTROLLER +static void dsi_init(py_display_obj_t *self) { + const display_mode_t *dm = &display_modes[self->framesize]; + + DSI_PLLInitTypeDef dsi_pllinit; + dsi_pllinit.PLLNDIV = 125; + dsi_pllinit.PLLIDF = DSI_PLL_IN_DIV4; + dsi_pllinit.PLLODF = DSI_PLL_OUT_DIV1; + uint32_t LANE_BYTE_CLOCK = 62500; + + display.hdsi.Instance = DSI; + display.hdsi.Init.NumberOfLanes = DSI_TWO_DATA_LANES; + display.hdsi.Init.TXEscapeCkdiv = 3; + display.hdsi.Init.AutomaticClockLaneControl = DSI_AUTO_CLK_LANE_CTRL_DISABLE; + HAL_DSI_Init(&display.hdsi, &dsi_pllinit); + + #if OMV_DSI_DISPLAY_TE_ENABLE + DSI_CmdCfgTypeDef dsi_cmd; + dsi_cmd.VirtualChannelID = self->vcid; + dsi_cmd.HSPolarity = DSI_HSYNC_ACTIVE_LOW; + dsi_cmd.VSPolarity = DSI_VSYNC_ACTIVE_LOW; + dsi_cmd.DEPolarity = DSI_DATA_ENABLE_ACTIVE_HIGH; + dsi_cmd.ColorCoding = DSI_RGB565; + dsi_cmd.CommandSize = 0xFFFFU; + dsi_cmd.TearingEffectSource = DSI_TE_DSILINK; + dsi_cmd.TearingEffectPolarity = DSI_TE_RISING_EDGE; + dsi_cmd.VSyncPol = DSI_VSYNC_FALLING; + dsi_cmd.AutomaticRefresh = DSI_AR_DISABLE; + dsi_cmd.TEAcknowledgeRequest = DSI_TE_ACKNOWLEDGE_ENABLE; + HAL_DSI_ConfigAdaptedCommandMode(&display.hdsi, &dsi_cmd); + #endif + + // Configure DSI PHY HS2LP and LP2HS timings + DSI_PHY_TimerTypeDef dsi_phyinit; + dsi_phyinit.ClockLaneHS2LPTime = 35; + dsi_phyinit.ClockLaneLP2HSTime = 35; + dsi_phyinit.DataLaneHS2LPTime = 35; + dsi_phyinit.DataLaneLP2HSTime = 35; + dsi_phyinit.DataLaneMaxReadTime = 0; + dsi_phyinit.StopWaitTime = 10; + HAL_DSI_ConfigPhyTimer(&display.hdsi, &dsi_phyinit); + HAL_DSI_ConfigFlowControl(&display.hdsi, DSI_FLOW_CONTROL_BTA); + HAL_DSI_SetLowPowerRXFilter(&display.hdsi, 10000); + HAL_DSI_ConfigErrorMonitor(&display.hdsi, HAL_DSI_ERROR_NONE); + + // Timing parameters for Video modes + DSI_VidCfgTypeDef dsi_vidcfg = { 0 }; + dsi_vidcfg.VirtualChannelID = self->vcid; + dsi_vidcfg.ColorCoding = DSI_RGB565; + dsi_vidcfg.LooselyPacked = DSI_LOOSELY_PACKED_DISABLE; + dsi_vidcfg.VSPolarity = DSI_VSYNC_ACTIVE_LOW; + dsi_vidcfg.HSPolarity = DSI_HSYNC_ACTIVE_LOW; + dsi_vidcfg.DEPolarity = DSI_DATA_ENABLE_ACTIVE_HIGH; + dsi_vidcfg.Mode = DSI_VID_MODE_BURST; + dsi_vidcfg.NullPacketSize = 0xFFF; + dsi_vidcfg.NumberOfChunks = 0; + dsi_vidcfg.PacketSize = self->width; + dsi_vidcfg.HorizontalSyncActive = dm->hsync_len * LANE_BYTE_CLOCK / dm->pixel_clock; + dsi_vidcfg.HorizontalBackPorch = dm->hback_porch * LANE_BYTE_CLOCK / dm->pixel_clock; + dsi_vidcfg.HorizontalLine = (self->width + dm->hsync_len + dm->hback_porch + dm->hfront_porch) + * LANE_BYTE_CLOCK / dm->pixel_clock; + dsi_vidcfg.VerticalSyncActive = dm->vsync_len; + dsi_vidcfg.VerticalBackPorch = dm->vback_porch; + dsi_vidcfg.VerticalFrontPorch = dm->vfront_porch; + dsi_vidcfg.VerticalActive = self->height; + + // Enable/disable sending LP command while streaming + dsi_vidcfg.LPCommandEnable = DSI_LP_COMMAND_ENABLE; + // Largest packet size possible to transmit in LP mode in VSA, VBP, VFP regions + dsi_vidcfg.LPLargestPacketSize = 0; + // Largest packet size possible to transmit in LP mode in HFP region during VACT period + dsi_vidcfg.LPVACTLargestPacketSize = 0; + // Specify for each region, if the going in LP mode is allowed while streaming + dsi_vidcfg.LPHorizontalFrontPorchEnable = DSI_LP_HFP_ENABLE; + dsi_vidcfg.LPHorizontalBackPorchEnable = DSI_LP_HBP_ENABLE; + dsi_vidcfg.LPVerticalActiveEnable = DSI_LP_VACT_ENABLE; + dsi_vidcfg.LPVerticalFrontPorchEnable = DSI_LP_VFP_ENABLE; + dsi_vidcfg.LPVerticalBackPorchEnable = DSI_LP_VBP_ENABLE; + dsi_vidcfg.LPVerticalSyncActiveEnable = DSI_LP_VSYNC_ENABLE; + + // Configure DSI Video mode timings with settings set above + HAL_DSI_ConfigVideoMode(&display.hdsi, &dsi_vidcfg); + HAL_DSI_Start(&display.hdsi); + HAL_DSI_Refresh(&display.hdsi); + HAL_DSI_PatternGeneratorStop(&display.hdsi); + + HAL_NVIC_SetPriority(DSI_IRQn, IRQ_PRI_LTDC, 0); + HAL_NVIC_EnableIRQ(DSI_IRQn); +} +#endif + static void ltdc_init(py_display_obj_t *self) { const display_mode_t *dm = &display_modes[self->framesize]; uint32_t fb_size = self->width * self->height * sizeof(uint16_t); @@ -240,12 +346,12 @@ static void ltdc_init(py_display_obj_t *self) { display.hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL, display.hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC, - display.hltdc.Init.HorizontalSync = dm->hsync_len -1; + display.hltdc.Init.HorizontalSync = dm->hsync_len - 1; display.hltdc.Init.VerticalSync = dm->vsync_len - 1; - display.hltdc.Init.AccumulatedHBP = dm->hsync_len + dm->hback_porch -1; - display.hltdc.Init.AccumulatedVBP = dm->vsync_len + dm->vback_porch -1; - display.hltdc.Init.AccumulatedActiveW = dm->hsync_len + dm->hback_porch + self->width -1; - display.hltdc.Init.AccumulatedActiveH = dm->vsync_len + dm->vback_porch + self->height -1; + display.hltdc.Init.AccumulatedHBP = dm->hsync_len + dm->hback_porch - 1; + display.hltdc.Init.AccumulatedVBP = dm->vsync_len + dm->vback_porch - 1; + display.hltdc.Init.AccumulatedActiveW = dm->hsync_len + dm->hback_porch + self->width - 1; + display.hltdc.Init.AccumulatedActiveH = dm->vsync_len + dm->vback_porch + self->height - 1; display.hltdc.Init.TotalWidth = dm->hsync_len + dm->hback_porch + self->width + dm->hfront_porch - 1; display.hltdc.Init.TotalHeigh = dm->vsync_len + dm->vback_porch + self->height + dm->vfront_porch - 1; @@ -281,8 +387,8 @@ void HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef *hltdc) { } static void display_write(py_display_obj_t *self, image_t *src_img, int dst_x_start, int dst_y_start, - float x_scale, float y_scale, rectangle_t *roi, int rgb_channel, int alpha, - const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint) { + float x_scale, float y_scale, rectangle_t *roi, int rgb_channel, int alpha, + const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint) { image_t dst_img; dst_img.w = self->width; dst_img.h = self->height; @@ -412,7 +518,50 @@ static void display_set_backlight(py_display_obj_t *self, uint32_t intensity) { } #endif // OMV_DISPLAY_BL_PIN +#ifdef OMV_DSI_DISPLAY_CONTROLLER +int display_dsi_write(py_display_obj_t *self, uint8_t cmd, uint8_t *args, size_t n_args, bool dcs) { + HAL_StatusTypeDef status = HAL_ERROR; + if (n_args == 0) { + status = HAL_DSI_ShortWrite(&display.hdsi, self->vcid, (dcs == true) ? + DSI_DCS_SHORT_PKT_WRITE_P0 : DSI_GEN_SHORT_PKT_WRITE_P1, cmd, 0x00); + } else if (n_args == 1) { + status = HAL_DSI_ShortWrite(&display.hdsi, self->vcid, (dcs == true) ? + DSI_DCS_SHORT_PKT_WRITE_P1 : DSI_GEN_SHORT_PKT_WRITE_P2, cmd, args[0]); + } else { + status = HAL_DSI_LongWrite(&display.hdsi, self->vcid, (dcs == true) ? + DSI_DCS_LONG_PKT_WRITE : DSI_GEN_LONG_PKT_WRITE, n_args, cmd, args); + } + if (status != HAL_OK) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("DSI write command failed")); + } + return 0; +} + +int display_dsi_read(py_display_obj_t *self, uint8_t cmd, uint8_t *args, + size_t n_args, uint8_t *buf, size_t len, bool dcs) { + HAL_StatusTypeDef status = HAL_ERROR; + uint8_t params[] = { cmd, (args == NULL) ? 0 : args[0] }; + if (n_args == 0) { + // For generic commands, the HAL expects cmd in ParametersTable[0U] + status = HAL_DSI_Read(&display.hdsi, self->vcid, buf, len, (dcs == true) ? + DSI_DCS_SHORT_PKT_READ : DSI_GEN_SHORT_PKT_READ_P1, cmd, params); + } else if (n_args == 1) { + status = HAL_DSI_Read(&display.hdsi, self->vcid, + buf, len, DSI_GEN_SHORT_PKT_READ_P2, 0, params); + } + if (status != HAL_OK) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("DSI read command failed")); + } + return 0; +} +#endif + static void display_deinit(py_display_obj_t *self) { + #ifdef OMV_DSI_DISPLAY_CONTROLLER + HAL_DSI_DeInit(&display.hdsi); + HAL_NVIC_DisableIRQ(DSI_IRQn); + #endif + HAL_LTDC_DeInit(&display.hltdc); HAL_NVIC_DisableIRQ(LTDC_IRQn); @@ -435,13 +584,15 @@ static void display_deinit(py_display_obj_t *self) { } mp_obj_t display_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_framesize, ARG_refresh, ARG_display_on, ARG_triple_buffer, ARG_portrait }; + enum { ARG_framesize, ARG_refresh, ARG_display_on, ARG_triple_buffer, ARG_portrait, ARG_channel, ARG_controller }; static const mp_arg_t allowed_args[] = { { MP_QSTR_framesize, MP_ARG_INT, {.u_int = DISPLAY_RESOLUTION_FWVGA } }, - { MP_QSTR_refresh, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 60 } }, + { MP_QSTR_refresh, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 60 } }, { MP_QSTR_display_on, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, { MP_QSTR_triple_buffer, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} }, { MP_QSTR_portrait, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} }, + { MP_QSTR_channel, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0 } }, + { MP_QSTR_controller, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, }; // Parse args. @@ -456,7 +607,12 @@ mp_obj_t display_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, } py_display_obj_t *self = (py_display_obj_t *) m_new_obj_with_finaliser(py_display_obj_t); + #ifdef OMV_DSI_DISPLAY_CONTROLLER + self->base.type = &py_dsi_display_type; + #else self->base.type = &py_rgb_display_type; + #endif + self->vcid = args[ARG_channel].u_int; self->refresh = args[ARG_refresh].u_int; self->display_on = args[ARG_display_on].u_bool; self->bgr = false; @@ -471,16 +627,32 @@ mp_obj_t display_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, self->width = display_modes[self->framesize].hactive; self->height = display_modes[self->framesize].vactive; } + self->controller = args[ARG_controller].u_obj; // Store state to access it from IRQ handlers or callbacks display.self = self; - + // Configure PLL3 for the selected mode clock. pll_config(self->framesize, self->refresh); // Init LTDC controller ltdc_init(self); + #ifdef OMV_DSI_DISPLAY_CONTROLLER + // Init DSI controller + dsi_init(self); + + // Init the display controller. + if (self->controller != mp_const_none) { + mp_obj_t dest[3]; + mp_load_method_maybe(self->controller, MP_QSTR_init, dest); + if (dest[0] != MP_OBJ_NULL) { + dest[2] = MP_OBJ_FROM_PTR(self); + mp_call_method_n_kw(1, 0, dest); + } + } + #endif + #ifdef OMV_DISPLAY_BL_PIN if (self->display_on) { display_set_backlight(self, 255); // to on state @@ -497,8 +669,13 @@ STATIC const py_display_p_t py_display_p = { #ifdef OMV_DISPLAY_BL_PIN .set_backlight = display_set_backlight, #endif + #ifdef OMV_DSI_DISPLAY_CONTROLLER + .dsi_write = display_dsi_write, + .dsi_read = display_dsi_read, + #endif }; +#ifdef OMV_RGB_DISPLAY_CONTROLLER MP_DEFINE_CONST_OBJ_TYPE( py_rgb_display_type, MP_QSTR_RGBDisplay, @@ -507,4 +684,17 @@ MP_DEFINE_CONST_OBJ_TYPE( protocol, &py_display_p, locals_dict, &py_display_locals_dict ); +#endif + +#ifdef OMV_DSI_DISPLAY_CONTROLLER +MP_DEFINE_CONST_OBJ_TYPE( + py_dsi_display_type, + MP_QSTR_DSIDisplay, + MP_TYPE_FLAG_NONE, + make_new, display_make_new, + protocol, &py_display_p, + locals_dict, &py_display_locals_dict + ); +#endif + #endif // MICROPY_PY_DISPLAY diff --git a/src/omv/ports/stm32/stm32fxxx_hal_msp.c b/src/omv/ports/stm32/stm32fxxx_hal_msp.c index d9e4b9cf4..28df1b33c 100644 --- a/src/omv/ports/stm32/stm32fxxx_hal_msp.c +++ b/src/omv/ports/stm32/stm32fxxx_hal_msp.c @@ -601,7 +601,19 @@ void HAL_JPEG_MspDeInit(JPEG_HandleTypeDef *hjpeg) { } #endif -#if defined(OMV_RGB_DISPLAY_CONTROLLER) +#if defined(OMV_DSI_DISPLAY_CONTROLLER) +void HAL_DSI_MspInit(DSI_HandleTypeDef *hdsi) { + __HAL_RCC_DSI_CLK_ENABLE(); +} + +void HAL_DSI_MspDeInit(DSI_HandleTypeDef *hdsi) { + __HAL_RCC_DSI_FORCE_RESET(); + __HAL_RCC_DSI_RELEASE_RESET(); + __HAL_RCC_DSI_CLK_DISABLE(); +} +#endif + +#if defined(OMV_RGB_DISPLAY_CONTROLLER) || defined(OMV_DSI_DISPLAY_CONTROLLER) void HAL_LTDC_MspInit(LTDC_HandleTypeDef *hltdc) { #if defined(OMV_RGB_DISPLAY_R0_PIN) const omv_gpio_t ltdc_pins[] = {