本篇讲述兆易GD32H759I-EVAL开发板LVGL移植,并通过按键切换界面显示。
一.了解LVGL并做准备
1.了解LVGL
LVGL(轻量级和通用图形库)是一个免费和开源的图形库,它提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素,美丽的视觉效果和低内存占用。
主要特性
丰富且强大的模块化图形组件:按钮 (buttons)、图表 (charts)、列表 (lists)、滑动条 (sliders)、图片 (images) 等
高级的图形引擎:动画、抗锯齿、透明度、平滑滚动、图层混合等效果
支持多种输入设备:触摸屏、 键盘、编码器、按键等
支持多显示设备
不依赖特定的硬件平台,可以在任何显示屏上运行
配置可裁剪(最低资源占用:64 kB Flash,16 kB RAM)
基于UTF-8的多语种支持,例如中文、日文、韩文、阿拉伯文等
可以通过类CSS的方式来设计、布局图形界面(例如:Flexbox、Grid)
支持操作系统、外置内存、以及硬件加速(LVGL已内建支持STM32 DMA2D、NXP PXP和VGLite)
即便仅有单缓冲区(frame buffer)的情况下,也可保证渲染如丝般顺滑
全部由C编写完成,并支持C++调用
支持Micropython编程,参见:LVGL API in Micropython
支持模拟器仿真,可以无硬件依托进行开发
丰富详实的例程
详尽的文档以及API参考手册,可线上查阅或可下载为PDF格式
在 MIT 许可下免费和开源
配置要求:
基本上,每个能够驱动显示器的现代控制器都适合运行 LVGL。 最低要求是:
16、32 或 64 位微控制器或处理器
建议使用 >16 MHz 时钟速度
闪存/ROM: > 64 kB 用于非常重要的组件 (> 建议使用 180 kB)
静态 RAM 使用量:~2 kB,取决于使用的功能和对象类型
堆: > 2kB (> 建议使用 8 kB)
动态数据(堆): > 2 KB (> 如果使用多个对象,建议使用 16 kB). 在 lv_conf.h 文件中配置 LV_MEM_SIZE 生效。
显示缓冲区:> “水平分辨率”像素(推荐 >10 × 10ד 水平分辨率”)
MCU或外部显示控制器中的一个帧缓冲区
C99 或更新的编译器
2.LVGL资源获取。这里使用LVGL v8.3,获取地址https://github.com/lvgl/lvgl/tree/release/v8.3
二.LVGL移植与应用
1.LVGL使用到的文件资源如下:
2.修改源文件
这里修改lv_conf_template.h为lv_conf.h,lv_port_disp_template.c/h为lv_port_disp.c/h,lv_port_indev_template.c/h为lv_port_indev.c/h,并使能#if 1 /**/ #endif。屏分辨率480*272,宏定义之。
#define LV_HOR_RES_MAX 480
#define LV_VER_RES_MAX 272
3.工程添加源文件
4.工程添加头文件路径
5.添加定时器为 LVGL 提供心跳
void TIMER1_IRQHandler(void)
{
// static uint16_t count=0;
if(SET == timer_interrupt_flag_get(TIMER1, TIMER_INT_FLAG_UP))
{
/* clear channel 0 interrupt bit */
timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_UP);
// count++;
// if(count>=10000)
// {
// count=0;
// printf("Run lv_tick_inc\r\n");
// }
lv_tick_inc(1);
}
}
void timer_config(void)
{
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER1);
timer_deinit(TIMER1);
/* TIMER0 configuration */
timer_initpara.prescaler = 2999;//299;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 99;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER1, &timer_initpara);
timer_enable(TIMER1);
timer_interrupt_flag_clear(TIMER1, TIMER_INT_FLAG_UP);
timer_interrupt_enable(TIMER1, TIMER_INT_UP);
nvic_irq_enable(TIMER1_IRQn, 1, 1);
}
6.DAM初始化。主要可用来将数据发送到TLIframe buff中。这里个人使用了用于不用DMA两种方式,后面会有所体现。
void dma_config(void)
{
dma_multi_data_parameter_struct dma_init_parameter;
/* peripheral clock enable */
rcu_periph_clock_enable(RCU_DMA1);
/* DMA peripheral configure */
dma_deinit(DMA1,DMA_CH0);
dma_init_parameter.periph_width = DMA_PERIPH_WIDTH_16BIT;
dma_init_parameter.periph_inc = DMA_PERIPH_INCREASE_ENABLE;
dma_init_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;
dma_init_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_parameter.memory_burst_width = DMA_MEMORY_BURST_4_BEAT;
dma_init_parameter.periph_burst_width = DMA_PERIPH_BURST_4_BEAT;
dma_init_parameter.critical_value = DMA_FIFO_4_WORD;
dma_init_parameter.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
dma_init_parameter.direction = DMA_MEMORY_TO_MEMORY;
dma_init_parameter.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_multi_data_mode_init(DMA1,DMA_CH0,&dma_init_parameter);
nvic_irq_enable(DMA1_Channel0_IRQn, 0, 0);
}
7.LCD初始化
LCD初始化,图层使能加载。
void lcd_config(void)
{
/* configure the GPIO of TLI */
tli_gpio_config();
/* configure TLI peripheral */
tli_config();
}
void lcd_init(void)
{
tli_layer_enable(LAYER0);
tli_layer_disable(LAYER1);
tli_reload_config(TLI_RL_FBR);
/* enable TLI */
tli_enable();
}
图层参数配置如下
8.LCD显示驱动移植
LVGL 的显示驱动文件主要修改 lv_prot_disp.h 和 lv_prot_disp.c 文件.开发板外挂一颗32MB SDRAM,代码中将 TLI中 frame buffer 定义到地址SDRAM_DEVICE0_ADDR 即0x0c000000
lv_port_disp_init修改如下,这里选用第一个。
disp_flush实现如下,这里通过宏LVGL_DMA选择用于不用DMA传输数据。
/*Flush the content of the internal buffer the specific area on the display
*You can use DMA or any hardware acceleration to do this operation in the background but
*'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
if(disp_flush_enabled) {
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
#if (!LVGL_DMA)
int32_t x;
int32_t y;
for(y = area->y1; y <= area->y2; y++) {
for(x = area->x1; x <= area->x2; x++) {
/*Put a pixel to the display. For example:*/
/*put_px(x, y, *color_p)*/
memcpy((uint32_t)&my_fb[y * LV_HOR_RES_MAX +x],(uint32_t)color_p,2);//1
// memcpy((&my_fb[y * LV_HOR_RES_MAX +x],color_p,2);//1
color_p++;
}
}
#else
if(area->x2 < 0)
{
return;
}
if(area->y2 < 0)
{
return;
}
if(area->x1 > LV_HOR_RES_MAX - 1)
{
return;
}
if(area->y1 > LV_VER_RES_MAX - 1)
{
return;
}
/*Truncate the area to the screen*/
int32_t act_x1 = area->x1 < 0 ? 0 : area->x1;
int32_t act_y1 = area->y1 < 0 ? 0 : area->y1;
int32_t act_x2 = area->x2 > LV_HOR_RES_MAX - 1 ? LV_HOR_RES_MAX - 1 : area->x2;
int32_t act_y2 = area->y2 > LV_VER_RES_MAX - 1 ? LV_VER_RES_MAX - 1 : area->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;
dma_transfer((uint32_t)buf_to_flush, (uint32_t)&my_fb[y_fill_act * LV_HOR_RES_MAX +x1_flush],(x2_flush - x1_flush + 1));
#endif
}
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
9.DMA传输数据如下
10.lv_conf.h配置
这里可将LV_MEM_SIZE适当改大,根据需要使能字体大小宏,根据需要使能组件例程,这里是能了LV_USE_DEMO_WIDGETS和LV_USE_DEMO_MUSIC。
11.main函数
int main(void)
{
BaseType_t ret;
/* enable the CPU cache */
cache_enable();
/* initialize the LEDs */
test_status_led_init();
/* configure systick */
systick_config();
/* flash the LEDs for 2 time */
led_flash(2);
/* configure USART0 */
usart_config();
/* configure TAMPER key */
gd_eval_key_init(KEY_TAMPER, KEY_MODE_EXTI);
/* output a message on hyperterminal using printf function */
printf("\r\n ================= LVGL ================= \r\n");
/**********************LCD Application**********************/
/* config the EXMC access mode */
exmc_synchronous_dynamic_ram_init(EXMC_SDRAM_DEVICE0);
printf("\r\nSDRAM Init \r\n");
timer_config();
lcd_config();
lcd_init();
/* configure the GPIO of SPI touch panel */
touch_panel_gpio_configure();
#if (LVGL_DMA)
delay_1ms(50);
dma_config();
delay_1ms(1000);
#endif
lv_init();
lv_port_disp_init();
lv_port_indev_init();
// lv_demo_music();
lv_demo_widgets();
while(1)
{
delay_1ms(5);
lv_task_handler();
}
}
12.按键切换显示。默认显示的经典画面lv_demo_widgets,按键切换显示音乐界面。
/*!
\brief this function handles external lines 10 to 15 interrupt request
\param[in] none
\param[out] none
\retval none
*/
void EXTI10_15_IRQHandler(void)
{
static uint8_t gui_show=0;
if(RESET != exti_interrupt_flag_get(EXTI_13))
{
printf("Key Pressed\r\n");
exti_interrupt_flag_clear(EXTI_13);
lv_demo_music();
}
}
三.测验
编译烧录后,LCD显示widgets界面见下图。按按键后,切换播放音乐动画界面,见下视频。
至此,实现兆易GD32H759I-EVAL开发板.LVGL移植。
引用: Jacktang 发表于 2024-6-2 09:03 兆易GD32H759I-EVAL开发板.LVGL移植成功,赞一个
也是弄了一段时间,中间有遇到些问题,最终解决移植成功。工程代码稍晚点时候分享出来,可供大家参考学习。