openmv/src/omv/ports/stm32/modules/py_display.c
2024-02-15 13:43:03 -08:00

668 lines
25 KiB
C

/*
* This file is part of the OpenMV project.
*
* Copyright (c) 2013-2021 Ibrahim Abdelkader <iabdalkader@openmv.io>
* Copyright (c) 2013-2021 Kwabena W. Agyeman <kwagyeman@openmv.io>
*
* This work is licensed under the MIT license, see the file LICENSE for details.
*
* LTDC/DSI display Python module.
*/
#include "omv_boardconfig.h"
#if MICROPY_PY_DISPLAY && \
(defined(OMV_RGB_DISPLAY_CONTROLLER) || \
defined(OMV_DSI_DISPLAY_CONTROLLER))
#include "py/obj.h"
#include "py/runtime.h"
#include "mphal.h"
#include "py_helper.h"
#include "py_image.h"
#include "omv_gpio.h"
#include "py_display.h"
#if defined(OMV_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_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
typedef struct display_mode {
uint32_t hactive;
uint32_t vactive;
uint32_t pixel_clock;
uint32_t hsync_len;
uint32_t hback_porch;
uint32_t hfront_porch;
uint32_t vsync_len;
uint32_t vback_porch;
uint32_t vfront_porch;
uint32_t hpol : 1;
uint32_t vpol : 1;
} display_mode_t;
typedef struct _display_state {
py_display_obj_t *self;
LTDC_HandleTypeDef hltdc;
#ifdef OMV_DSI_DISPLAY_CONTROLLER
DSI_HandleTypeDef hdsi;
#endif
LTDC_LayerCfgTypeDef framebuffer_layers[FRAMEBUFFER_COUNT];
} display_state_t;
static display_state_t display;
static const display_mode_t display_modes[] = {
{ // QVGA
.hactive = 320, .vactive = 240, .pixel_clock = 6144,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 1
},
{ // TQVGA
.hactive = 240, .vactive = 320, .pixel_clock = 6426,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 1
},
{ // FHVGA
.hactive = 480, .vactive = 272, .pixel_clock = 9633,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 1
},
{ // FHVGA2
.hactive = 480, .vactive = 128, .pixel_clock = 4799,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 1
},
{ // VGA
.hactive = 640, .vactive = 480, .pixel_clock = 21363,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 1
},
{ // THVGA
.hactive = 320, .vactive = 480, .pixel_clock = 11868,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 1
},
{ // FWVGA
.hactive = 800, .vactive = 480, .pixel_clock = 26110,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 1
},
{ // FWVGA2
.hactive = 800, .vactive = 320, .pixel_clock = 17670,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 1
},
{ // TFWVGA
.hactive = 480, .vactive = 800, .pixel_clock = 27624,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 9
},
{ // TFWVGA2
.hactive = 480, .vactive = 480, .pixel_clock = 16615,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 1
},
{ // SVGA
.hactive = 800, .vactive = 600, .pixel_clock = 32597,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 4
},
{ // WSVGA
.hactive = 1024, .vactive = 600, .pixel_clock = 40895,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 4
},
{ // XGA
.hactive = 1024, .vactive = 768, .pixel_clock = 52277,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 8
},
{ // SXGA
.hactive = 1280, .vactive = 1024, .pixel_clock = 85920,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 16
},
{ // SXGA2
.hactive = 1280, .vactive = 400, .pixel_clock = 33830,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 1
},
{ // UXGA
.hactive = 1600, .vactive = 1200, .pixel_clock = 124364,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 21
},
{ // HD
.hactive = 1280, .vactive = 720, .pixel_clock = 60405,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 7
},
{ // FHD
.hactive = 1920, .vactive = 1080, .pixel_clock = 133187,
.hsync_len = 32, .hback_porch = 40, .hfront_porch = 8,
.vsync_len = 8, .vback_porch = 6, .vfront_porch = 17
},
};
void LTDC_IRQHandler() {
HAL_LTDC_IRQHandler(&display.hltdc);
}
#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;
for (uint32_t divm = 1; divm <= 63; divm++) {
for (uint32_t divr = 1; divr <= 128; divr++) {
uint32_t vci = 0;
uint32_t ref_clk = (HSE_VALUE / 1000) / divm;
if (1000 <= ref_clk && ref_clk <= 2000) {
vci = RCC_PLL3VCIRANGE_0;
} else if (2000 <= ref_clk && ref_clk <= 4000) {
vci = RCC_PLL3VCIRANGE_1;
} else if (4000 <= ref_clk && ref_clk <= 8000) {
vci = RCC_PLL3VCIRANGE_2;
} else if (8000 <= ref_clk && ref_clk <= 16000) {
vci = RCC_PLL3VCIRANGE_3;
} else {
continue;
}
uint32_t vco = 0;
uint32_t pll_clk = pixel_clock * divr;
if (150000 <= pll_clk && pll_clk <= 420000) {
vco = RCC_PLL3VCOMEDIUM;
} else if (192000 <= pll_clk && pll_clk <= 836000) {
vco = RCC_PLL3VCOWIDE;
} else {
continue;
}
uint32_t divn = pll_clk / ref_clk;
if (divn < 4 || 512 < divn) {
continue;
}
uint32_t frac = ((pll_clk % ref_clk) * 8192) / ref_clk;
RCC_PeriphCLKInitTypeDef init;
init.PeriphClockSelection = RCC_PERIPHCLK_LTDC;
init.PLL3.PLL3M = divm;
init.PLL3.PLL3N = divn;
init.PLL3.PLL3P = 128;
init.PLL3.PLL3Q = 128;
init.PLL3.PLL3R = divr;
init.PLL3.PLL3RGE = vci;
init.PLL3.PLL3VCOSEL = vco;
init.PLL3.PLL3FRACN = frac;
if (HAL_RCCEx_PeriphCLKConfig(&init) == HAL_OK) {
return;
}
}
}
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unable to initialize LTDC PLL!"));
}
static void get_display_mode(display_mode_t *dm_out, uint32_t framesize, bool portrait) {
const display_mode_t *dm = &display_modes[framesize];
if (portrait == false) {
*dm_out = *dm;
} else {
dm_out->hactive = dm->vactive;
dm_out->vactive = dm->hactive;
dm_out->hsync_len = dm->vsync_len;
dm_out->vsync_len = dm->hsync_len;
dm_out->hback_porch = dm->vback_porch;
dm_out->vback_porch = dm->hback_porch;
dm_out->hfront_porch = dm->vfront_porch;
dm_out->vfront_porch = dm->hfront_porch;
dm_out->pixel_clock = dm->pixel_clock;
}
}
#ifdef OMV_DSI_DISPLAY_CONTROLLER
static void dsi_init(py_display_obj_t *self) {
display_mode_t dm;
get_display_mode(&dm, self->framesize, self->portrait);
uint32_t pixel_clock = (dm.pixel_clock * self->refresh) / 60;
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 = 4;
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 = dm.hactive;
dsi_vidcfg.HorizontalSyncActive = dm.hsync_len * LANE_BYTE_CLOCK / pixel_clock;
dsi_vidcfg.HorizontalBackPorch = dm.hback_porch * LANE_BYTE_CLOCK / pixel_clock;
dsi_vidcfg.HorizontalLine = (dm.hactive + dm.hsync_len + dm.hback_porch + dm.hfront_porch)
* LANE_BYTE_CLOCK / pixel_clock;
dsi_vidcfg.VerticalSyncActive = dm.vsync_len;
dsi_vidcfg.VerticalBackPorch = dm.vback_porch;
dsi_vidcfg.VerticalFrontPorch = dm.vfront_porch;
dsi_vidcfg.VerticalActive = dm.vactive;
// 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) {
display_mode_t dm;
get_display_mode(&dm, self->framesize, self->portrait);
uint32_t fb_size = dm.hactive * dm.vactive * sizeof(uint16_t);
fb_alloc_mark();
for (int i = 0; i < FRAMEBUFFER_COUNT; i++) {
self->framebuffers[i] = (uint16_t *) fb_alloc0(fb_size, FB_ALLOC_CACHE_ALIGN);
display.framebuffer_layers[i].WindowX0 = 0;
display.framebuffer_layers[i].WindowX1 = dm.hactive;
display.framebuffer_layers[i].WindowY0 = 0;
display.framebuffer_layers[i].WindowY1 = dm.vactive;
display.framebuffer_layers[i].PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
display.framebuffer_layers[i].Alpha = 0;
display.framebuffer_layers[i].Alpha0 = 0;
display.framebuffer_layers[i].BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA;
display.framebuffer_layers[i].BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA;
display.framebuffer_layers[i].FBStartAdress = (uint32_t) self->framebuffers[i];
display.framebuffer_layers[i].ImageWidth = dm.hactive;
display.framebuffer_layers[i].ImageHeight = dm.vactive;
display.framebuffer_layers[i].Backcolor.Blue = 0;
display.framebuffer_layers[i].Backcolor.Green = 0;
display.framebuffer_layers[i].Backcolor.Red = 0;
}
fb_alloc_mark_permanent();
display.hltdc.Instance = LTDC;
display.hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AH,
display.hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL,
display.hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL,
display.hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC,
display.hltdc.Init.HorizontalSync = dm.hsync_len - 1;
display.hltdc.Init.VerticalSync = dm.vsync_len - 1;
display.hltdc.Init.AccumulatedHBP = dm.hsync_len + dm.hback_porch - 1;
display.hltdc.Init.AccumulatedVBP = dm.vsync_len + dm.vback_porch - 1;
display.hltdc.Init.AccumulatedActiveW = dm.hsync_len + dm.hback_porch + dm.hactive - 1;
display.hltdc.Init.AccumulatedActiveH = dm.vsync_len + dm.vback_porch + dm.vactive - 1;
display.hltdc.Init.TotalWidth = dm.hsync_len + dm.hback_porch + dm.hactive + dm.hfront_porch - 1;
display.hltdc.Init.TotalHeigh = dm.vsync_len + dm.vback_porch + dm.vactive + dm.vfront_porch - 1;
display.hltdc.Init.Backcolor.Blue = 0;
display.hltdc.Init.Backcolor.Green = 0;
display.hltdc.Init.Backcolor.Red = 0;
HAL_LTDC_Init(&display.hltdc);
NVIC_SetPriority(LTDC_IRQn, IRQ_PRI_LTDC);
HAL_NVIC_EnableIRQ(LTDC_IRQn);
// Start interrupt chain.
HAL_LTDC_Reload(&display.hltdc, LTDC_RELOAD_VERTICAL_BLANKING);
}
void HAL_LTDC_ReloadEventCallback(LTDC_HandleTypeDef *hltdc) {
py_display_obj_t *self = display.self;
HAL_LTDC_ConfigLayer_NoReload(&display.hltdc,
&display.framebuffer_layers[self->framebuffer_tail], LTDC_LAYER_1);
// Continue chain...
HAL_LTDC_Reload(&display.hltdc, LTDC_RELOAD_VERTICAL_BLANKING);
#if defined(OMV_DISPLAY_DISP_PIN)
if (self->display_on && (self->framebuffer_tail != self->framebuffer_head)) {
// Turn display on if there is a new command.
omv_gpio_write(OMV_DISPLAY_DISP_PIN, 1);
}
#endif
self->framebuffer_head = self->framebuffer_tail;
}
static void display_write(py_display_obj_t *self, image_t *src_img, int dst_x_start, int dst_y_start,
float x_scale, float y_scale, rectangle_t *roi, int rgb_channel, int alpha,
const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint) {
image_t dst_img;
dst_img.w = self->width;
dst_img.h = self->height;
dst_img.pixfmt = PIXFORMAT_RGB565;
point_t p0, p1;
imlib_draw_image_get_bounds(&dst_img, src_img, dst_x_start, dst_y_start, x_scale, y_scale,
roi, alpha, alpha_palette, hint, &p0, &p1);
bool black = p0.x == -1;
// For triple buffering we are never drawing where tail or head (which may instantly update to
// to be equal to tail) is.
int tail = (self->framebuffer_tail + 1) % FRAMEBUFFER_COUNT;
if (tail == self->framebuffer_head) {
tail = (tail + 1) % FRAMEBUFFER_COUNT;
}
dst_img.data = (uint8_t *) self->framebuffers[tail];
// Set default values for the layer to display the whole framebuffer.
display.framebuffer_layers[tail].WindowX0 = black ? 0 : p0.x;
display.framebuffer_layers[tail].WindowX1 = black ? self->width : p1.x;
display.framebuffer_layers[tail].WindowY0 = black ? 0 : p0.y;
display.framebuffer_layers[tail].WindowY1 = black ? self->height : p1.y;
display.framebuffer_layers[tail].Alpha = black ? 0 : fast_roundf((alpha * 255) / 256.f);
display.framebuffer_layers[tail].FBStartAdress =
black ? ((uint32_t) dst_img.data) : ((uint32_t) (IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&dst_img, p0.y) + p0.x));
display.framebuffer_layers[tail].ImageWidth = black ? self->width : dst_img.w;
display.framebuffer_layers[tail].ImageHeight = black ? self->height : (p1.y - p0.y);
// Set alpha to 256 here as we will use the layer alpha to blend the image into the background color of black for free.
if (!black) {
imlib_draw_image(&dst_img, src_img, dst_x_start, dst_y_start,
x_scale, y_scale, roi, rgb_channel, 256, color_palette,
alpha_palette, hint | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL, NULL);
}
#ifdef __DCACHE_PRESENT
// Flush data for DMA
if (!black) {
SCB_CleanDCache_by_Addr((uint32_t *) dst_img.data, image_size(&dst_img));
}
#endif
// Update tail which means a new image is ready.
self->framebuffer_tail = tail;
}
static void display_clear(py_display_obj_t *self, bool off) {
#if defined(OMV_DISPLAY_DISP_PIN)
if (self->display_on && off) {
// turns the display off (may not be black)
omv_gpio_write(OMV_DISPLAY_DISP_PIN, 0);
return;
}
#endif
// For triple buffering we are never drawing where tail or head (which may instantly update to
// to be equal to tail) is.
int tail = (self->framebuffer_tail + 1) % FRAMEBUFFER_COUNT;
if (tail == self->framebuffer_head) {
tail = (tail + 1) % FRAMEBUFFER_COUNT;
}
// Set default values for the layer to display the whole framebuffer.
display.framebuffer_layers[tail].WindowX0 = 0;
display.framebuffer_layers[tail].WindowX1 = self->width;
display.framebuffer_layers[tail].WindowY0 = 0;
display.framebuffer_layers[tail].WindowY1 = self->height;
display.framebuffer_layers[tail].Alpha = 0;
display.framebuffer_layers[tail].FBStartAdress = (uint32_t) self->framebuffers[tail];
display.framebuffer_layers[tail].ImageWidth = self->width;
display.framebuffer_layers[tail].ImageHeight = self->height;
// Update tail which means a new image is ready.
self->framebuffer_tail = tail;
}
#ifdef OMV_DISPLAY_BL_PIN
static void display_set_backlight(py_display_obj_t *self, uint32_t intensity) {
omv_gpio_config(OMV_DISPLAY_BL_PIN, OMV_GPIO_MODE_OUTPUT, OMV_GPIO_PULL_NONE, OMV_GPIO_SPEED_LOW, -1);
omv_gpio_write(OMV_DISPLAY_BL_PIN, !!intensity);
}
#endif
#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);
__HAL_RCC_PLL3_DISABLE();
uint32_t tickstart = mp_hal_ticks_ms();
while (__HAL_RCC_GET_FLAG(RCC_FLAG_PLL3RDY)) {
if ((mp_hal_ticks_ms() - tickstart) > PLL_TIMEOUT_VALUE) {
break;
}
}
#ifdef OMV_DISPLAY_BL_PIN
omv_gpio_deinit(OMV_DISPLAY_BL_PIN);
#endif
fb_alloc_free_till_mark_past_mark_permanent();
}
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, ARG_channel, ARG_controller, ARG_backlight
};
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_framesize, MP_ARG_INT, {.u_int = DISPLAY_RESOLUTION_FWVGA } },
{ MP_QSTR_refresh, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 60 } },
{ MP_QSTR_display_on, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
{ MP_QSTR_triple_buffer, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
{ MP_QSTR_portrait, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
{ 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} },
{ MP_QSTR_backlight, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} },
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
if ((args[ARG_framesize].u_int < 0) || (args[ARG_framesize].u_int >= DISPLAY_RESOLUTION_MAX)) {
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Frame Size!"));
}
if ((args[ARG_refresh].u_int < 30) || (args[ARG_refresh].u_int > 120)) {
mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid Refresh Rate!"));
}
py_display_obj_t *self = (py_display_obj_t *) m_new_obj_with_finaliser(py_display_obj_t);
#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;
self->triple_buffer = args[ARG_triple_buffer].u_bool;
self->framebuffer_tail = 0;
self->framebuffer_head = 0;
self->framesize = args[ARG_framesize].u_int;
self->portrait = args[ARG_portrait].u_bool;
if (self->portrait) {
self->width = display_modes[self->framesize].vactive;
self->height = display_modes[self->framesize].hactive;
} else {
self->width = display_modes[self->framesize].hactive;
self->height = display_modes[self->framesize].vactive;
}
self->controller = args[ARG_controller].u_obj;
self->bl_controller = args[ARG_backlight].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
return MP_OBJ_FROM_PTR(self);
}
STATIC const py_display_p_t py_display_p = {
.deinit = display_deinit,
.clear = display_clear,
.write = display_write,
#ifdef OMV_DISPLAY_BL_PIN
.set_backlight = display_set_backlight,
#endif
#ifdef OMV_DSI_DISPLAY_CONTROLLER
.bus_write = display_dsi_write,
.bus_read = display_dsi_read,
#endif
};
#ifdef OMV_RGB_DISPLAY_CONTROLLER
MP_DEFINE_CONST_OBJ_TYPE(
py_rgb_display_type,
MP_QSTR_RGBDisplay,
MP_TYPE_FLAG_NONE,
make_new, display_make_new,
protocol, &py_display_p,
locals_dict, &py_display_locals_dict
);
#endif
#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