Porting LVGL on STM32H743 with DMA, LTDC, and SDRAM on a custom board

How can I make my RGB565 parallel display work with DMA?

What MCU/Processor/Board and compiler are you using?

I am using the STM32H743IIT6 on a custom board and using STM32CubeIDE as the compiler.

What LVGL version are you using?

I am using LVGL version 8.3.

What do you want to achieve?

I want to port LVGL on my STM32H743 using DMA.

What have you tried so far?

I have successfully used the polling mode (which involves using two nested “for” loops in the disp_flush function) to port LVGL on my STM32H743, and it works fine.

i have read this example lv_port_stm32f746_disco and get help from it.

here is my code (i put all related codes in main.c)

#define MY_DISP_HOR_RES 800
#define MY_DISP_VER_RES 480

static int32_t x1_flush;
static int32_t y1_flush;
static int32_t x2_flush;
static int32_t y2_fill;
static int32_t y_fill_act;
static const lv_color_t * buf_to_flush;
static lv_disp_drv_t disp_drv; /Descriptor of a display driver/

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
static void ex_disp_clean_dcache(lv_disp_drv_t *drv);
static void DMA_TransferComplete(DMA_HandleTypeDef *han);
static void DMA_TransferError(DMA_HandleTypeDef *han);

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

LTDC_HandleTypeDef hltdc;

DMA_HandleTypeDef hdma_memtomem_dma2_stream0;
SDRAM_HandleTypeDef hsdram1;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_FMC_Init(void);
static void MX_LTDC_Init(void);
/
USER CODE BEGIN PFP */

uint16_t * lcd_fb = (uint16_t*) (0xC0000000);

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------/
/
USER CODE BEGIN 0 */

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{

int32_t x1= area->x1;
int32_t x2= area->x2;
int32_t y1= area->y1;
int32_t y2= area->y2;

/Return if the area is out the screen/

if(x2 < 0) return;
if(y2 < 0) return;
if(x1 > MY_DISP_HOR_RES - 1) return;
if(y1 > MY_DISP_VER_RES - 1) return;

/*Truncate the area to the screen*/
int32_t act_x1 = x1 < 0 ? 0 : x1;
int32_t act_y1 = y1 < 0 ? 0 : y1;
int32_t act_x2 = x2 > MY_DISP_HOR_RES - 1 ? MY_DISP_HOR_RES - 1 : x2;
int32_t act_y2 = y2 > MY_DISP_VER_RES - 1 ? MY_DISP_VER_RES - 1 : y2;

x1_flush = act_x1;
y1_flush = act_y1;
x2_flush = act_x2;
y2_fill = act_y2;
y_fill_act = act_y1;
buf_to_flush = color_p;

SCB_CleanInvalidateDCache();
SCB_InvalidateICache();


 /*##-7- Start the DMA transfer using the interrupt mode #*/
	/* Configure the source, destination and buffer size DMA fields and Start DMA Stream transfer */
	/* Enable All the DMA interrupts */
	HAL_StatusTypeDef err;
	uint32_t length = (x2_flush - x1_flush + 1);

	err = HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0,
			(uint32_t)buf_to_flush,
			(uint32_t) &lcd_fb[y_fill_act * MY_DISP_HOR_RES + x1_flush],
			 length);


	if(err != HAL_OK)
	{
		while(1);	/*Halt on error*/
	}

/*IMPORTANT!!!
 *Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);

}

static void DMA_TransferComplete(DMA_HandleTypeDef *han)
{
y_fill_act ++;

if(y_fill_act > y2_fill)
{
	SCB_CleanInvalidateDCache();
	SCB_InvalidateICache();
    lv_disp_flush_ready(&disp_drv);
}
else
{
	uint32_t length = (x2_flush - x1_flush + 1);
    buf_to_flush += x2_flush - x1_flush + 1;
    /*##-7- Start the DMA transfer using the interrupt mode ####################*/
    /* Configure the source, destination and buffer size DMA fields and Start DMA Stream transfer */
    /* Enable All the DMA interrupts */

    if(HAL_DMA_Start_IT(    han,
    		(uint32_t)buf_to_flush,
    		(uint32_t)&lcd_fb[y_fill_act * MY_DISP_HOR_RES + x1_flush],
             length) != HAL_OK)
    {
        while(1);	/*Halt on error*/
    }
}

}

static void DMA_TransferError(DMA_HandleTypeDef *han){}

static void ex_disp_clean_dcache(lv_disp_drv_t *drv)
{
SCB_CleanInvalidateDCache();
}

void lv_port_disp_init(void)
{

 MX_DMA_Init();

 static lv_disp_draw_buf_t draw_buf_dsc_1;
 static lv_color_t buf1_1[MY_DISP_HOR_RES * 10];
 lv_disp_draw_buf_init(&draw_buf_dsc_1, buf1_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize 
 the display buffer*/

/*-----------------------------------
 * Register the display in LVGL
 *----------------------------------*/

lv_disp_drv_init(&disp_drv);

/*Set the resolution of the display*/
disp_drv.hor_res = MY_DISP_HOR_RES;/*800*/
disp_drv.ver_res = MY_DISP_VER_RES;/*480*/

/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_flush;
disp_drv.clean_dcache_cb = ex_disp_clean_dcache;

/*Set a display buffer*/
disp_drv.draw_buf = &draw_buf_dsc_1;

/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);

}

/**

  • Enable DMA controller clock
  • Configure DMA for memory to memory transfers
  • hdma_memtomem_dma2_stream0
    */
    static void MX_DMA_Init(void)
    {

/* DMA controller clock enable */
__HAL_RCC_DMA2_CLK_ENABLE();

/* Configure DMA request hdma_memtomem_dma2_stream0 on DMA2_Stream0 */
hdma_memtomem_dma2_stream0.Instance = DMA2_Stream0;
hdma_memtomem_dma2_stream0.Init.Request = DMA_REQUEST_MEM2MEM;
hdma_memtomem_dma2_stream0.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma2_stream0.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma2_stream0.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem_dma2_stream0.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_memtomem_dma2_stream0.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_memtomem_dma2_stream0.Init.Mode = DMA_NORMAL;
hdma_memtomem_dma2_stream0.Init.Priority = DMA_PRIORITY_HIGH;
hdma_memtomem_dma2_stream0.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
hdma_memtomem_dma2_stream0.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_1QUARTERFULL;
hdma_memtomem_dma2_stream0.Init.MemBurst = DMA_MBURST_SINGLE;
hdma_memtomem_dma2_stream0.Init.PeriphBurst = DMA_PBURST_SINGLE;
if (HAL_DMA_Init(&hdma_memtomem_dma2_stream0) != HAL_OK)
{
Error_Handler( );
}

HAL_DMA_RegisterCallback(&hdma_memtomem_dma2_stream0,HAL_DMA_XFER_CPLT_CB_ID, DMA_TransferComplete);
HAL_DMA_RegisterCallback(&hdma_memtomem_dma2_stream0,HAL_DMA_XFER_ERROR_CB_ID, DMA_TransferError);

/* DMA interrupt init /
/
DMA2_Stream0_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);

}

int main(void)
{
/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/* MCU Configuration--------------------------------------------------------*/

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();

/* USER CODE BEGIN Init */

/* USER CODE END Init */

/* Configure the system clock */
SystemClock_Config();

/* USER CODE BEGIN SysInit */

/* USER CODE END SysInit */

/* Initialize all configured peripherals /
MX_GPIO_Init();
MX_FMC_Init();
MX_LTDC_Init();
/
USER CODE BEGIN 2 */

lv_init();

lv_port_disp_init();

lv_example_get_started_1();

/* USER CODE END 2 */

/* Infinite loop /
/
USER CODE BEGIN WHILE /
while (1)
{
/
USER CODE END WHILE */

/* USER CODE BEGIN 3 */

  lv_timer_handler();
  HAL_Delay(10);

}
/* USER CODE END 3 */
}

and it is my result

When I debug the code, I realize that my code doesn’t reach the function disp_flush , and it gets stuck at lv_timer_handler() . Subsequently, I encounter a HardFault error at the end.

/**

  • @brief This function handles Hard fault interrupt.
    /
    void HardFault_Handler(void)
    {
    /
    USER CODE BEGIN HardFault_IRQn 0 */

/* USER CODE END HardFault_IRQn 0 /
while (1)
{
/
USER CODE BEGIN W1_HardFault_IRQn 0 /
/
USER CODE END W1_HardFault_IRQn 0 */
}
}

my code for download

stm32h7xx_it.c (6.0 KB)
main.c (16.0 KB)

Appreciate your help in advance.

i mean primary mistake is this in flush end. With DMA isnt flush completed here , only in callback. And Mem2Mem full block is ok only for x1=0 x2=width. You now use line by line.
Exist more ways how to handle this. But for LTDC is maybe more effective lvgl direct mode.

Second error you dont show how you handle lv_inc_tick

1 Like

Currently I am unable to analyze your problem, but his example may be helpful.

Appreciate your answer. I saw the example, which was discussing how to use DMA2D, but I want to use DMA instead.

Appreciate your answer. I used lv_tick_inc(1) in the SysTick_Handler function, which is located in the interrupt file.

I made some changes to my code. Now, I am using two frame buffers instead of one for the lv_disp_draw_buf_init() function and removed the following two functions from the code:

SCB_CleanInvalidateDCache();
SCB_InvalidateICache();

When I enable Dcache and Icache, I don’t get any display.

You mentioned that I should use a callback function for DMA, and I believe I have done so by using the DMA_TransferComplete function. If I am mistaken, please correct me.

After I made these changes I only have a display for this demo, “lv_demo_widgets();”,

However, for the other demos such as “lv_demo_benchmark();” and “lv_demo_stress();”, my code didn’t work properly. Here are the results:

Thanks for your advice.

Use DMA to copy LVGL buffer to LTDC memory? It is possible, but only line-by-line when exactly the same color coding is used. Generic DMA is not aware of the placement of the window being blended within the whole screen. It is simply not capable of applying the required offsets and strides.
With DMA2D you can also copy typical ARGB32 LVGL buffer to usual LTDC RGB24 memory and save some RAM bytes. Generic DMA cannot do this for you.

Thank you for your guidance. The problem has been solved.

We should not call ‘lv_disp_flush_ready(disp_drv)’ at the end of the disp_flush function in DMA mode, and there is no problem to enable I_cache and d_cache. This enabling helps to achieve better performance, as I found from benchmark results.

Best regards.

Hello dear Jastrzębski,

I have been following your guidance and studying your example that implements DMA2D without Pixel Format Conversion or Blending. I’ve been going through the steps as you suggested in this example(stm32f769-DISCO-LVGL). However, I’m encountering issues. When LV_USE_GPU_STM32_DMA2D is set to 0, everything seems to be fine, and the lv_demo_widgets() function works as expected. However, when I switch to using the lv_demo_benchmark() function, the display shows no movement. Additionally, when LV_USE_GPU_STM32_DMA2D is set to 1, everything becomes problematic and code didt work.

Here is my code.
lvgl version : 8.3.9


#define LCD_FB_START_ADDRESS       ((uint32_t)0xC0000000)
#define LCD_BPP                    (16/8)             // bytes per pixel
#define MY_DISP_HOR_RES            800
#define MY_DISP_VER_RES            480
#define DRAW_BUFFER_SIZE           (MY_DISP_HOR_RES * MY_DISP_VER_RES / 10)

#define AL(x, n) (x % n == 0 ? x : x + n - (x % n))  // align length
#define CACHE_ROW_SIZE 32U
#define DRAW_BUFFER_SIZE_ALIGNED AL(DRAW_BUFFER_SIZE, CACHE_ROW_SIZE / sizeof(lv_color_t))

static lv_disp_drv_t disp_drv;
ALIGN_32BYTES(static lv_color_t _lvDrawBuffer[DRAW_BUFFER_SIZE_ALIGNED]);  


static void FlushBufferComplete(DMA2D_HandleTypeDef* hdma2d)
{
	lv_disp_flush_ready(&disp_drv);
}

void lv_port_disp_init(void)
{
	/*in here we just use one buffer not two buffer as i found*/
     static lv_disp_draw_buf_t draw_buf_dsc_1;

     lv_disp_draw_buf_init(&draw_buf_dsc_1, _lvDrawBuffer , NULL, DRAW_BUFFER_SIZE);   
    /*Initialize the display buffer*/

    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/

    lv_disp_drv_init(&disp_drv);

    /*Set the resolution of the display*/
    disp_drv.hor_res = MY_DISP_HOR_RES;/*800*/
    disp_drv.ver_res = MY_DISP_VER_RES;/*480*/

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;

    hdma2d.XferCpltCallback = FlushBufferComplete;

    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_1;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);



}
static void disp_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * buffer)
{

	uint32_t width = lv_area_get_width(area);
	uint32_t height = lv_area_get_height(area);
	uint32_t bufferLength = width * height * LCD_BPP;
	uint16_t x = area->x1;
	uint16_t y = area->y1;

	// copy buffer using DMA2D without Pixel Format Conversion (PFC) or Blending
	uint32_t destination = LCD_FB_START_ADDRESS + LCD_BPP*(y * MY_DISP_HOR_RES + x);
     SCB_CleanDCache_by_Addr((uint32_t*)buffer, bufferLength);
	hdma2d.Init.Mode = DMA2D_M2M;
	hdma2d.Init.OutputOffset = (MY_DISP_HOR_RES - width);
	hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565;
	hdma2d.Instance = DMA2D;

	HAL_DMA2D_Init(&hdma2d);
	HAL_DMA2D_ConfigLayer(&hdma2d, 0);
	HAL_DMA2D_Start_IT(&hdma2d,(uint32_t)buffer,destination,width,height);

}
int main(void)
{

  SCB_EnableICache();

  SCB_EnableDCache();

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();
  MX_FMC_Init();
  MX_LTDC_Init();

  lv_init();

  lv_port_disp_init();

// lv_demo_widgets();

 lv_demo_benchmark();

  while (1)
  {

	  HAL_Delay(5);
	  lv_task_handler();

  }

}
void SysTick_Handler(void)
{
 
  HAL_IncTick();
  lv_tick_inc(1);
}

I would appreciate your assistance in resolving this matter.

Maybe is required before ltdc init

MX_DMA2D_Init();

and set hdma2d.LayerCfg params
some info STM32CubeH7/Projects/STM32H743I-EVAL/Examples/DMA2D/DMA2D_MemoryToMemory/Src/main.c at master · STMicroelectronics/STM32CubeH7 (github.com)

1 Like

Thanks in advance. The problem is with setting up hdma2d.LayerCfg parameters ,as you mentoined.