STM32 DMA2D with LVGL

STM32F429 개발 보드가 도착하여 LVGL 최신 버전인 8.0.2 버전으로 포팅하려고 하며, 아래 LVGL GitHub의 STM32F429 Disc 데모보드에 포팅한 예제를 함께 참고하였다.

 

GitHub - lvgl/lv_port_stm32f429_disco: LVGL ported to STM32F429I-DISC1 using SW4STM32 (Ac6) IDE

LVGL ported to STM32F429I-DISC1 using SW4STM32 (Ac6) IDE - GitHub - lvgl/lv_port_stm32f429_disco: LVGL ported to STM32F429I-DISC1 using SW4STM32 (Ac6) IDE

github.com

LVGL 라이브러리는 STM32 DMA2D를 사용할 수 있도록 이미 포팅되어 있으며, DMA2D는 'Chrom-ART Accelerator ™ (DMA2D)는 이미지 조작 전용 DMA'라고 한다. LVGL 라이브러리가 STM32 DMA2D를 사용하기 위해 lv_conf.h 파일을 아래와 같이 수정하도록 한다.

개발보드가 STM32F429이기 때문에 stm32f429xx.h로 설정하였다. 아래 코드와 같이 LVGL에서 사용할 dispbuf를 설정해야 한다. 개발보드의 LCD는 1024 * 600 해상도를 가지며 32비트 컬러를 지원하고 있다.

void lvgl_init(void)
{
    lv_init();
    lv_disp_draw_buf_init(&disp_buf, (void *)MEM_ADDR_LVGL_DISP_BUF, NULL, LCD_WIDTH*LCD_HEIGHT);

    lv_disp_drv_init(&disp_drv);
	disp_drv.draw_buf   = &disp_buf;
	disp_drv.flush_cb   = dispbuf_flush;
	disp_drv.monitor_cb = NULL;
	disp_drv.hor_res    = LCD_WIDTH;
	disp_drv.ver_res    = LCD_HEIGHT;
	lv_disp_drv_register(&disp_drv);
}

dispbuf_flush는 dispbuf 내용을 LCD FrameBuffer에 복사(전송)하면 LCD 화면에 dispbuf의 내용이 출력된다. dispbuf에서 LCD로 데이터를 전송할 때 DMA를 사용해야 하며, dispbuf에서 LCD로 데이터를 전송하는 방법도 LVGL 라이브러리와 마찬가지기로 DMA2D를 동일하게 사용하였다.

static void dma2d_transfer(uint32_t data, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
    DMA2D_HandleTypeDef hdma2d;

    hdma2d.Instance          = DMA2D;
    hdma2d.Init.Mode         = DMA2D_M2M;
    hdma2d.Init.ColorMode    = DMA2D_OUTPUT_ARGB8888;
    hdma2d.Init.OutputOffset = LCD_WIDTH - width;

    // Foreground
    hdma2d.LayerCfg[1].AlphaMode      = DMA2D_NO_MODIF_ALPHA;
    hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_ARGB8888;
    hdma2d.LayerCfg[1].InputOffset    = 0;

    if(HAL_DMA2D_Init(&hdma2d) == 0)
    {
        uint32_t fb_offset = MEM_ADDR_FRAME_BUF + (x + y * LCD_WIDTH) * 4;

        HAL_DMA2D_ConfigLayer(&hdma2d, 1);
        HAL_DMA2D_Start(&hdma2d, data, fb_offset, width, height);
        HAL_DMA2D_PollForTransfer(&hdma2d, 10);
    }
}

static void dispbuf_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p)
{
    uint16_t act_x1, act_x2, act_y1, act_y2;

	if(area->x2 < 0 || area->y2 < 0)
        return;

	if(area->x1 > (LCD_WIDTH - 1) || area->y1 > (LCD_HEIGHT - 1))
        return;

	act_x1 = area->x1 < 0 ? 0 : area->x1;
	act_y1 = area->y1 < 0 ? 0 : area->y1;
	act_x2 = area->x2 > LCD_WIDTH - 1 ? LCD_WIDTH - 1 : area->x2;
	act_y2 = area->y2 > LCD_HEIGHT - 1 ? LCD_HEIGHT - 1 : area->y2;

	dma2d_transfer((uint32_t)color_p, act_x1, act_y1, act_x2 - act_x1 + 1, act_y2 - act_y1 + 1);
	lv_disp_flush_ready(&disp_drv);
}

아래 영상은 1초 단위로 디코딩된 PNG 배경 이미지 및 여러 LVGL 위젯을 그리고 지우는 과정을 반복한 것이며, CPU 부하 및 메모리 사용량을 체크하였는데, CPU는 최대 40% 내외로 사용하는 것을 확인할 수 있다. 만약 DMA를 사용하지 않았다면 CPU 사용량은 더 높았을 것이다.

다음 포스트에서는 GPIO Input을 LVGL 라이브러리 키패드로 연결하는 방법을 정리할 예정이다.