简介
ESP32-32出色的性价比,较好的性能与内存空间,可以好利用来完成GUI显示库的加载
LVGL
LVGL是一款比较流行的致力于MCU与MPU创建漂亮UI的嵌入式图形库,免费且开源。
硬件
硬件采用的是正点原子的ESP32-S3
屏幕使用的是SPI通信方式,配合IO口控制(RST,A0),来实现LCD屏幕的驱动
移植步骤
LVGL移植总的步骤主要是如下几步
1.调用lv_init();
2.初始化驱动
3.注册显示与输入驱动,显存的配置,显示响应回调函数的响应
4.lv_tick_inc(x) 在中断中定时更新,x设定取决于lv_tick_inc的调用频率
5.lv_timer_handler,定时调用,完成LVGL的响应(更新LVGL的响应)
具体示例
拷贝一个可以正常驱动LCD的工程
拷贝LVGL(V8.3.0)代码至工程,ESP32需要在指定的路径components下
由于ESP32并不需要去修改lv_conf.h这个文件来配置LVGL,可以通过设置项来修改LVGL的配置,具体的配置机制原理未深入了解
配置LVGL
在默认的设置下勾选MUSIC DEMO,由于MUSCI DEMO中还用到了其他字体,还需要勾选 Montserrat 12与Montserrat 16这两种字体
此时编译工程,应该是可以编译通过
移植LVGL相关代码
SemaphoreHandle_t xGuiSemaphore;
void lvgl_demo(void)
{
lv_init(); //LVGL初始化前都需要调用
lv_port_disp_init(); //显示驱动的移植,初始化及配对,输出
lv_port_indev_init(); //输入驱动的移植
xGuiSemaphore = xSemaphoreCreateMutex();
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &increase_lvgl_tick,
.name = "lvgl_tick"
};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, 1 * 1000)); //创建定时器,更新LVGL的内部时钟基准
lv_demo_music();
while(1)
{
if( pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY))
{
lv_timer_handler(); //定时调用,更新LVGL的实现
xSemaphoreGive(xGuiSemaphore);
}
vTaskDelay(pdMS_TO_TICKS(5));
}
}
static void increase_lvgl_tick(void *arg)
{
lv_tick_inc(1);
}
显示初始化与绑定代码
void lv_port_disp_init(void)
{
void *buf1 = NULL;
buf1 = heap_caps_malloc(lcd_self.width * 10 * sizeof(lv_color_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); //申请显存
static lv_disp_draw_buf_t disp_buf;
lv_disp_draw_buf_init(&disp_buf, buf1, NULL, lcd_self.width * 10); //绑定显存,显存绑定有多种形式,这是一种开销最小的单显存设置
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = lcd_self.width; //屏幕宽度
disp_drv.ver_res = lcd_self.height; //屏幕高度
disp_drv.flush_cb = lvgl_disp_flush_cb;
disp_drv.draw_buf = &disp_buf;
lv_disp_drv_register(&disp_drv); //初始化显示屏幕尺寸,并绑定屏幕更新函数
}
static void lvgl_disp_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
lcd_color_fill(area->x1, area->y1, area->x2, area->y2, (uint16_t*)color_map);//显示屏区域更新像素点颜色,像素颜色在数组中
lv_disp_flush_ready(drv); //发送更新完毕,这一句代码必须要有
}
输出驱动里面如果是空的也可以完成显示的移植,只是没有输入功能
demo示例,参考的正点原子提供的代码,有没有都不影响MUSIC DEMO的显示
MUSCI DEMO对这几个按键也没有响应,加上了,也无法判断是否已正常工作
void lv_port_indev_init(void)
{
static lv_indev_drv_t indev_drv;
keypad_init();
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_KEYPAD;
indev_drv.read_cb = keypad_read;
indev_keypad = lv_indev_drv_register(&indev_drv);
}
uint32_t g_last_key = 0;
void keypad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
uint32_t act_key = keypad_get_key();
if(act_key != 0)
{
data->state = LV_INDEV_STATE_PR;
switch(act_key)
{
case KEY0_PRES:
act_key = LV_KEY_RIGHT;
break;
case KEY1_PRES:
act_key = LV_KEY_NEXT;
//back_act_key = KEY1_PRES;
break;
case KEY2_PRES:
act_key = LV_KEY_LEFT;
break;
case KEY3_PRES:
act_key = LV_KEY_ENTER;
break;
default:
break;
}
g_last_key = act_key;
}
else
{
data->state = LV_INDEV_STATE_REL;
g_last_key = 0;
}
data->key = g_last_key;
}
uint32_t keypad_get_key(void)
{
return xl9555_key_scan(0);
}
显示效果
V9.1.0移植
大体步骤与V8.3.0一致,期间出了一些问题,在这里也记录下
屏幕驱动API有些变化,更新程序的形参考声明有所变化,直接转换成uint16_t*的16位RGB565颜色也可以正常显示,暂时没有深究具体原因
void lv_port_disp_init(void)
{
void *buf1 = NULL;
buf1 = heap_caps_malloc(lcd_self.width * 10 * 4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
lv_display_t *disp = lv_display_create(lcd_self.width, lcd_self.height);
lv_display_set_flush_cb(disp, lvgl_disp_flush_cb);
lv_display_set_buffers(disp, buf1, NULL, lcd_self.width * 10 * 4, LV_DISPLAY_RENDER_MODE_PARTIAL);
}
static void lvgl_disp_flush_cb(lv_disp_t *drv, const lv_area_t *area, uint8_t *px_map)
{
lcd_color_fill(area->x1, area->y1, area->x2, area->y2, (uint16_t*)px_map);
lv_disp_flush_ready(drv);
}
无法正常显示,提示栈溢出
原因在于任务栈太小了,导致任务运行出错,增大任务栈即可正常工作。LVGL的使用,相对来说使用的STACK较深,系统默认的配置就不太够用
总结
LVGL需要配置显存跟内存,同时也需要考虑任务栈的问题,使用的过程中一定也会遇到很多问题,资料相对较少要自己多加摸索。
经过上述步骤 LVGL可以在ESP32上面运行DEMO,不过这个离会用LVGL还有一定的距离。
还需要查看更多的LVGL资料,以及ESP32-IDF编程的资料,才能进一步熟悉并使用。