上篇文章,在N9H30
开发板上跑了一下RTThread下的LVGL例程。接下来慢慢分析一下例程代码。熟悉一下整个框架,从例程为突破口逐步去学习LVGL。
对于LVGL的底层适配,RTThread官方已经给搭建好了。先看最开始对LVGL的初始化,代码在“packages/LVGL-v8.2.0/env_support/rt-thread/lv_rt_thread_port.c”内。lv_port_init()函数已经加到了自动运行里面,无需用户调用。如下:
sta
tic int lv_port_init(void)
{
#if LV_USE_LOG
lv_log_register_print_cb(lv_rt_log);
#endif
lv_init(); //初始化LVGL基础部件
lv_port_disp_init(); //初始化显示器设备接口
lv_port_indev_init(); //初始化输入设备接口(触摸屏)
return 0;
}
INIT_COMPONENT_EXPORT(lv_port_init);
其中lv_init()代码如下,主要初始化了一些链表。每种链表的功能后面用到了再慢慢分析。这里绕了半天的是对这些链表的声明。
void lv_init(void)
{
/*Do nothing if already initialized*/
if(lv_initialized) {
LV_LOG_WARN("lv_init: already inited");
return;
}
LV_LOG_INFO("begin");
/*Initialize the misc modules*/
lv_mem_init(); //RTThread提供了内存管理,所以这里没有做任何操作
_lv_timer_core_init(); //初始化LVGL相关的软件定时器,初始化定时器链表,开启定时器
_lv_fs_init(); //初始化LVGL实现的顶层文件系统(加入了文件缓存),初始化文件系统链表
_lv_anim_core_init(); //初始化动画相关代码,初始化动画链表,创建并关闭动画定时器
_lv_group_init(); //group初始化,初始化group的链表
lv_draw_init(); //绘图初始化,这里什么都没做
#if LV_USE_GPU_
STM32_DMA2D
/*Initialize DMA2D GPU*/
lv_draw_stm32_dma2d_init();
#endif
#if LV_USE_GPU_NXP_PXP && LV_USE_GPU_NXP_PXP_AUTO_INIT
if(lv_gpu_nxp_pxp_init(&pxp_default_cfg) != LV_RES_OK) {
LV_LOG_ERROR("PXP init error. STOP.n");
for(; ;) ;
}
#endif
_lv_obj_style_init(); //初始化style链表
_lv_ll_init(&LV_GC_ROOT(_lv_disp_ll), sizeof(lv_disp_t)); //初始化disp链表
_lv_ll_init(&LV_GC_ROOT(_lv_indev_ll), sizeof(lv_indev_t)); //初始化indev链表
/*Initialize the screen refresh system*/
_lv_refr_init();
_lv_img_decoder_init();
#if LV_IMG_CACHE_DEF_SIZE
lv_img_cache_set_size(LV_IMG_CACHE_DEF_SIZE);
#endif
/*Test if the IDE has UTF-8 encoding*/
char * txt = "脕";
uint8_t * txt_u8 = (uint8_t *)txt;
if(txt_u8[0] != 0xc3 || txt_u8[1] != 0x81 || txt_u8[2] != 0x00) {
LV_LOG_WARN("The strings have no UTF-8 encoding. Non-ASCII characters won't be displayed.");
}
uint32_t endianess_test = 0x11223344;
uint8_t * endianess_test_p = (uint8_t *) &endianess_test;
bool big_endian = endianess_test_p[0] == 0x11 ? true : false;
if(big_endian) {
LV_ASSERT_MSG(LV_BIG_ENDIAN_SYSTEM == 1,
"It's a big endian system but LV_BIG_ENDIAN_SYSTEM is not enabled in lv_conf.h");
}
else {
LV_ASSERT_MSG(LV_BIG_ENDIAN_SYSTEM == 0,
"It's a little endian system but LV_BIG_ENDIAN_SYSTEM is enabled in lv_conf.h");
}
#if LV_USE_ASSERT_MEM_INTEGRITY
LV_LOG_WARN("Memory integrity checks are enabled via LV_USE_ASSERT_MEM_INTEGRITY which makes LVGL much slower");
#endif
#if LV_USE_ASSERT_OBJ
LV_LOG_WARN("Object sanity checks are enabled via LV_USE_ASSERT_OBJ which makes LVGL much slower");
#endif
#if LV_USE_ASSERT_STYLE
LV_LOG_WARN("Style sanity checks are enabled that uses more RAM");
#endif
#if LV_LOG_LEVEL == LV_LOG_LEVEL_TRACE
LV_LOG_WARN("Log level is set to 'Trace' which makes LVGL much slower");
#endif
lv_extra_init();
lv_initialized = true;
LV_LOG_TRACE("finished");
}
下面以_lv_timer_core_init()函数为例,分析一下对于链表_lv_timer_ll的声明和初始化,其他基本类似。
void _lv_timer_core_init(void)
{
_lv_ll_init(&LV_GC_ROOT(_lv_timer_ll), sizeof(lv_timer_t));
/*Initially enable the lv_timer handling*/
lv_timer_enable(true);
}
其中LV_GC_ROOT的定义为#define LV_GC_ROOT(x) x,所以_lv_ll_init函数的调用展开为_lv_ll_init(&_lv_timer_ll, sizeof(lv_timer_t)); _lv_ll_init函数的代码如下,很好理解,清空了链表,初始化了节点大小。这里最主要的是不知道传入的_lv_timer_ll链表头在哪定义的。比较绕,看了半天才看明白。
void _lv_ll_init(lv_ll_t * ll_p, uint32_t node_size)
{
ll_p->head = NULL;
ll_p->tail = NULL;
#ifdef LV_ARCH_64
/*Round the size up to 8*/
node_size = (node_size + 7) & (~0x7);
#else
/*Round the size up to 4*/
node_size = (node_size + 3) & (~0x3);
#endif
ll_p->n_size = node_size;
}
右键_lv_timer_ll变量,跳转声明,会调到下面代码。此代码在“lv_gc.c”内。LV_ROOTS追踪到底,就是对这些链表变量的声明。
#if(!defined(LV_ENABLE_GC)) || LV_ENABLE_GC == 0
LV_ROOTS
#endif /*LV_ENABLE_GC*/
如下所示,是相关的宏定义。
#define LV_DISPATCH(f, t, n) f(t, n)
#define LV_ITERATE_ROOTS(f)
LV_DISPATCH(f, lv_ll_t, _lv_timer_ll) /*Linked list to store the lv_timers*/
LV_DISPATCH(f, lv_ll_t, _lv_disp_ll) /*Linked list of display device*/
LV_DISPATCH(f, lv_ll_t, _lv_indev_ll) /*Linked list of input device*/
LV_DISPATCH(f, lv_ll_t, _lv_fsdrv_ll)
LV_DISPATCH(f, lv_ll_t, _lv_anim_ll)
LV_DISPATCH(f, lv_ll_t, _lv_group_ll)
LV_DISPATCH(f, lv_ll_t, _lv_img_decoder_ll)
LV_DISPATCH(f, lv_ll_t, _lv_obj_style_trans_ll)
LV_DISPATCH(f, lv_layout_dsc_t *, _lv_layout_list)
LV_DISPATCH_COND(f, _lv_img_cache_entry_t*, _lv_img_cache_array, LV_IMG_CACHE_DEF, 1)
LV_DISPATCH_COND(f, _lv_img_cache_entry_t, _lv_img_cache_single, LV_IMG_CACHE_DEF, 0)
LV_DISPATCH(f, lv_timer_t*, _lv_timer_act)
LV_DISPATCH(f, lv_mem_buf_arr_t , lv_mem_buf)
LV_DISPATCH_COND(f, _lv_draw_mask_radius_circle_dsc_arr_t , _lv_circle_cache, LV_DRAW_COMPLEX, 1)
LV_DISPATCH_COND(f, _lv_draw_mask_saved_arr_t , _lv_draw_mask_list, LV_DRAW_COMPLEX, 1)
LV_DISPATCH(f, void * , _lv_theme_default_styles)
LV_DISPATCH(f, void * , _lv_theme_basic_styles)
LV_DISPATCH_COND(f, uint8_t *, _lv_font_decompr_buf, LV_USE_FONT_COMPRESSED, 1)
LV_DISPATCH(f, uint8_t * , _lv_grad_cache_mem)
#define LV_DEFINE_ROOT(root_type, root_name) root_type root_name;
#define LV_ROOTS LV_ITERATE_ROOTS(LV_DEFINE_ROOT)
上面宏定义乍看比较绕,看明白了其实也很简单,展开后就是如下的变量声明.个人没太想明白这样写除了让第一眼见到此代码的人有点难懂外,有哪些好处。
lv_ll_t _lv_timer_ll;
lv_ll_t _lv_disp_ll;
lv_ll_t _lv_indev_ll;
lv_ll_t _lv_fsdrv_ll;
lv_ll_t _lv_anim_ll;
lv_ll_t _lv_group_ll;
...
lv_port_disp_init()函数体如下。主要就是在RTThread的LCD驱动上层,实现了一层display设备。实现了disp设备的初始化和注册。
void lv_port_disp_init(void)
{
rt_err_t result;
void *buf1 = RT_NULL;
void *buf2 = RT_NULL;
uint32_t u32FBSize;
lcd_device = rt_device_find("lcd");
if (lcd_device == 0)
{
LOG_E("error!");
return;
}
/* get framebuffer address */
result = rt_device_control(lcd_device, RTGRAPHIC_CTRL_GET_INFO, &info);
if (result != RT_EOK)
{
LOG_E("error!");
/* get device information failed */
return;
}
/* Disable backlight at startup. */
rt_device_control(lcd_device, RTGRAPHIC_CTRL_POWEROFF, RT_NULL);
RT_ASSERT(info.bits_per_pixel == 8 || info.bits_per_pixel == 16 ||
info.bits_per_pixel == 24 || info.bits_per_pixel == 32);
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
/*Set the resolution of the display*/
disp_drv.hor_res = info.width;
disp_drv.ver_res = info.height;
u32FBSize = info.height * info.width * (info.bits_per_pixel / 8);
#if (LV_USE_ANTI_TEARING==1)
disp_drv.full_refresh = 1;
#endif
if (disp_drv.full_refresh)
{
#if (LV_USE_GPU_N9H30_GE2D==1)
buf1 = (void *)info.framebuffer; // Use Non-cacheable VRAM
#else
buf1 = (void *)((uint32_t)info.framebuffer & ~BIT31); // Use Cacheable VRAM
#endif
buf2 = (void *)((uint32_t)buf1 + u32FBSize);
buf3_next = (void *)((uint32_t)buf2 + u32FBSize);
rt_kprintf("LVGL: Use triple screen-sized buffers(full_refresh) - buf1@%08x, buf2@%08x, buf3_next@%08xn", buf1, buf2, buf3_next);
disp_drv.flush_cb = nu_flush_full_refresh;
}
else
{
buf1 = (void *)(((uint32_t)info.framebuffer) + u32FBSize);
buf2 = (void *)((uint32_t)buf1 + u32FBSize);
rt_kprintf("LVGL: Use two screen-sized buffers - buf1@%08x, buf2@%08xn", buf1, buf2);
rt_device_control(lcd_device, RTGRAPHIC_CTRL_PAN_DISPLAY, info.framebuffer);
disp_drv.flush_cb = nu_flush;
}
/*Initialize `disp_buf` with the buffer(s).*/
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, info.width * info.height);
result = rt_device_open(lcd_device, 0);
if (result != RT_EOK)
{
LOG_E("error!");
return;
}
/*Set a display buffer*/
disp_drv.draw_buf = &disp_buf;
#if LV_VERSION_EQUAL(8, 1, 0)
/*Fill a memory with a color (GPU only)*/
disp_drv.gpu_fill_cb = nu_fill_cb;
#endif
#if (LV_USE_GPU_N9H30_GE2D && LV_VERSION_CHECK(8, 2, 0))
disp_drv.draw_ctx_init = lv_draw_n9h30_ge2d_ctx_init;
disp_drv.draw_ctx_deinit = lv_draw_n9h30_ge2d_ctx_init;
disp_drv.draw_ctx_size = sizeof(lv_draw_n9h30_ge2d_ctx_t);
#endif
/*Called after every refresh cycle to tell the rendering and flushing time + the number of flushed pixels*/
//disp_drv.monitor_cb = nu_perf_monitor;
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
这里顺便说一下RTThread系统的LCD驱动代码在“libraries/n9h30/rtt_port/drv_vpost.c”内。初始化代码如下,主要初始化和注册了LCD设备。另外,framebuffer的空间也是在这里开辟的。开辟了3倍或者说3块显存。应该是用于加速刷新率或者分层显示用的。具体后面边学边分析。这里暂不深究。此代码也被加入到了自动调用里,无需用户调用。
int rt_hw_vpost_init(void)
{
int i = -1;
rt_err_t ret;
VPOST_T *psVpostLcmInst = vpostLCMGetInstance(VPOST_USING_LCD_IDX);
RT_ASSERT(psVpostLcmInst != RT_NULL);
if ((psVpostLcmInst->u32DevWidth * psVpostLcmInst->u32DevHeight) > (480 * 272))
{
/* LCD clock is selected from UPLL and divide to 20MHz */
outpw(REG_CLK_DIVCTL1, (inpw(REG_CLK_DIVCTL1) & ~0xff1f) | 0xE18);
/* LCD clock is selected from UPLL and divide to 30MHz */
//outpw(REG_CLK_DIVCTL1, (inpw(REG_CLK_DIVCTL1) & ~0xff1f) | 0x918);
}
else
{
/* LCD clock is selected from UPLL and divide to 10MHz */
outpw(REG_CLK_DIVCTL1, (inpw(REG_CLK_DIVCTL1) & ~0xff1f) | 0xE19);
}
/* Initial LCM */
vpostLCMInit(VPOST_USING_LCD_IDX);
/* Set scale to 1:1 */
vpostVAScalingCtrl(1, 0, 1, 0, VA_SCALE_INTERPOLATION);
for (i = eVpost_LCD; i < eVpost_Cnt; i++)
{
nu_vpost_t psVpost = &nu_fbdev[i];
rt_memset((void *)&psVpost->info, 0, sizeof(struct rt_device_graphic_info));
/* Register VPOST information */
psVpost->info.bits_per_pixel = BSP_LCD_BPP;
psVpost->info.pixel_format = (BSP_LCD_BPP == 32) ? RTGRAPHIC_PIXEL_FORMAT_ARGB888 : RTGRAPHIC_PIXEL_FORMAT_RGB565;
psVpost->info.pitch = psVpostLcmInst->u32DevWidth * (BSP_LCD_BPP / 8);
psVpost->info.width = psVpostLcmInst->u32DevWidth;
psVpost->info.height = psVpostLcmInst->u32DevHeight;
/* Get pointer of video frame buffer */
/* Set display color depth */
/* Note: before get pointer of frame buffer, must set display color depth first */
if (psVpost->layer == eVpost_LCD)
{
#if (BSP_LCD_BPP==32)
vpostSetVASrc(VA_SRC_RGB888);
#else
vpostSetVASrc(VA_SRC_RGB565);
#endif
psVpost->info.framebuffer = (rt_uint8_t *)vpostGetMultiFrameBuffer(DEF_VPOST_BUFFER_NUMBER);
}
#if defined(BSP_USING_VPOST_OSD)
else if (psVpost->layer == eVpost_OSD)
{
vpostOSDSetWindow(0, 0, psVpost->info.width, psVpost->info.height);
#if (BSP_LCD_BPP==32)
vpostSetOSDSrc(OSD_SRC_RGB888);
#else
vpostSetOSDSrc(OSD_SRC_RGB565);
#endif
psVpost->info.framebuffer = (rt_uint8_t *)vpostGetMultiOSDBuffer(DEF_VPOST_BUFFER_NUMBER);
}
#endif
if (psVpost->info.framebuffer == NULL)
{
rt_kprintf("Fail to get VRAM buffer.n");
RT_ASSERT(0);
}
else
{
uint32_t u32FBSize = psVpost->info.pitch * psVpostLcmInst->u32DevHeight;
psVpost->info.smem_len = u32FBSize * DEF_VPOST_BUFFER_NUMBER;
rt_memset(psVpost->info.framebuffer, 0, u32FBSize);
}
/* Register member functions of lcd device */
psVpost->dev.type = RT_Device_Class_Graphic;
psVpost->dev.init = vpost_layer_init;
psVpost->dev.open = vpost_layer_open;
psVpost->dev.close = vpost_layer_close;
psVpost->dev.control = vpost_layer_control;
/* Register graphic device driver */
ret = rt_device_register(&psVpost->dev, psVpost->name, RT_DEVICE_FLAG_RDWR);
RT_ASSERT(ret == RT_EOK);
if (psVpost->layer == eVpost_LCD)
{
rt_hw_interrupt_install(psVpost->irqn, nu_vpost_isr, psVpost, psVpost->name);
rt_hw_interrupt_umask(psVpost->irqn);
}
rt_kprintf("%s's fbmem at 0x%08x.n", psVpost->name, psVpost->info.framebuffer);
}
/* For saving memory bandwidth. */
vpostLCMDeinit();
return (int)ret;
}
INIT_DEVICE_EXPORT(rt_hw_vpost_init);
用到的LCD初始化结构体如下:
static VPOST_T DEF_FW070TFT =
{
800, /*!< Panel width */
480, /*!< Panel height */
0, /*!< MPU command line low indicator */
0, /*!< MPU command width */
0, /*!< MPU bus width */
VPOSTB_DATA16or18, /*!< Display bus width */
0, /*!< MPU mode */
VPOSTB_COLORTYPE_16M, /*!< Display colors */
VPOSTB_DEVICE_SYNC_HIGHCOLOR, /*!< Type of display panel */
0x020d0420, /*!< CRTCSIZE register value */
0x01e00320, /*!< CRTCDEND register value */
0x033e0339, /*!< CRTCHR register value */
0x040c03f8, /*!< CRTCHSYNC register value */
0x020001f6 /*!< CRTCVR register value */
};
lv_port_indev_init()初始化并注册了触摸屏的输入设备,链接了触摸屏的input_read回调接口。
void lv_port_indev_init(void)
{
static lv_indev_drv_t indev_drv;
/* Basic initialization */
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = input_read;
/* Register the driver in LVGL and save the created input device object */
lv_indev_drv_register(&indev_drv);
}
原作者:吉利咕噜2022