开发环境 VS Code
ESP32 TTGO TTGO屏幕135x240
LVGL全称Light and Versatile Graphics Library,是一个轻量级开源的嵌入式GUI库,界面精美,资源消耗小,可移植度高,全库采用纯 c 语言开发。
64 kB 闪存和 8 kB RAM 足以用于简单的用户集成
从 30 多个随时可以使用小部件中选择,并轻松自定义它们
在任何平台上使用 LVGL,如STM32、K210、Arduino、ESP32、树莓等
驱动单色、OLED、TFT 显示屏、监视器或任何其他显示器
arduino LVGL学习文档:https://docs.lvgl.io/latest/en/html/get-started/arduino.html#get-the-lvgl-ardunio-library
实现效果:实现图片显示,中文显示,文字滚动显示,绘制线条。
前段时间在K210移植了LVGL,跑着还可以,最近想拿ESP32试试,Arduino 有现成的LVGL库,可以直接使用,不需要自己在下载源码了。
使用LVGL自带的工具实现图片的转换。
在线图片转换:https://lvgl.io/tools/imageconverter
点击 Convert会生成一个cat.c文件,之后需要稍微修改一下cat.c,如果不修改会出现下面的错误:
修改的部分:
显示图片代码:
/* 引入图片,相当于extern */
LV_IMG_DECLARE(cat);
lv_obj_t *image1 = lv_img_create(lv_scr_act(),NULL);/* 创建image 控件*/
lv_img_set_src(image1,&cat); /* 为控件设置图片*/
lv_obj_align(image1,NULL,LV_ALIGN_IN_TOP_MID,0,0);/* 设置控件的对齐方式,相对坐标*/
中文的实现需要进行字体的取模,可以使用官方在线工具,也可以使用本地工具。
本地工具下载地址:https://wwi.lanzous.com/b00ob32qh
密码:haqs
在线工具:https://lvgl.io/tools/fontconverter
建议使用本地工具,本地工具直接调用系统字体进行取模转换,在线工具需要自己指定字体TTF文件。
点击保存,生成一个gb2312_puhui32.c的文件,里面包含字体文件。下一步,修改gb2312_puhui32.c文件。
汉字显示代码:
/* 引入字体,等同于 extern */
LV_FONT_DECLARE(gb2312_puhui32);
static lv_style_t style_cn; /* 定义新的样式,请注意使用 static 或者全局变量 */
lv_style_init(&style_cn);
lv_style_set_text_font(&style_cn, LV_STATE_DEFAULT, &gb2312_puhui32);
//汉字显示
lv_obj_t *label3 = lv_label_create(scr,NULL); /* 创建 label 控件 */
//lv_obj_set_style(label3,&style_cn); /* 为控件设置新的 style */
lv_obj_add_style(label3, LV_LABEL_PART_MAIN, &style_cn);
lv_obj_set_pos(label3,0,0); /* 设置控件的坐标 */
lv_label_set_text(label3,"小先生"); /* 设置文字 */
lv_obj_align(label3,NULL,LV_ALIGN_IN_TOP_MID,0,100); /* 设置控件的对齐方式-相对坐标 */
使用了FreeRTOS操作系统,LVGL运行需要时钟节拍,选择使用了定时器来产生时钟节拍,lv_task_handlerLVGL系统工作函数。
#include
#include
#include
#include
#include
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif
hw_timer_t * timer = NULL;
const int Timeout = 50;//ms
TFT_eSPI tft = TFT_eSPI(135,240); /* TFT instance */
static lv_disp_buf_t disp_buf;
static lv_color_t buf[LV_HOR_RES_MAX * 10];
void Task1( void *pvParameters );//
void Task2( void *pvParameters );//图片1
#if USE_LV_LOG != 0
/* Serial debugging */
void my_print(lv_log_level_t level, const char * file, uint32_t line, const char * dsc)
{
Serial.printf("%s@%d->%s\r\n", file, line, dsc);
Serial.flush();
}
#endif
/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
tft.startWrite();
tft.setAddrWindow(area->x1, area->y1, w, h);
tft.pushColors(&color_p->full, w * h, true);
tft.endWrite();
lv_disp_flush_ready(disp);
}
/* Reading input device (simulated encoder here) */
bool read_encoder(lv_indev_drv_t * indev, lv_indev_data_t * data)
{
static int32_t last_diff = 0;
int32_t diff = 0; /* Dummy - no movement */
int btn_state = LV_INDEV_STATE_REL; /* Dummy - no press */
data->enc_diff = diff - last_diff;;
data->state = btn_state;
last_diff = diff;
return false;
}
void IRAM_ATTR onTimer(){
lv_task_handler(); /* LVGL 工作函数 */
}
void setup()
{
Serial.begin(115200); /* prepare for possible serial debug */
lv_init();
timer = timerBegin(0, 80, true);//开启定时器 选择timer0,分频系数为80,向上计数
timerAttachInterrupt(timer, &onTimer, true);//开启定时器中断函数
timerAlarmWrite(timer, Timeout * 1000, true);// 保护值 开启自动重载
timerAlarmEnable(timer);//使能定时器
#if USE_LV_LOG != 0
lv_log_register_print_cb(my_print); /* register print function for debugging */
#endif
//tft.init();
tft.begin(); /* TFT init */
tft.setRotation(1); /* Landscape orientation */
tft.fillScreen(TFT_WHITE);
tft.setTextSize(2);
tft.setCursor(0, 0);
tft.setTextDatum(MC_DATUM);
void lv_style_init(void);
lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * 10);
/*Initialize the display*/
lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = 240;
disp_drv.ver_res = 135;
disp_drv.flush_cb = my_disp_flush;
disp_drv.buffer = &disp_buf;
lv_disp_drv_register(&disp_drv);
/*Initialize the (dummy) input device driver*/
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_ENCODER;
indev_drv.read_cb = read_encoder;
lv_indev_drv_register(&indev_drv);
xTaskCreatePinnedToCore(
Task1 //任务函数
, "Task1" /* 任务名字 没啥用,就是描述*/
, 1024 /*堆栈大小,单位为字节*/
, NULL /*作为任务输入传递的参数*/
, 1 // /*任务的优先级*/Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
, NULL
, ARDUINO_RUNNING_CORE); //指定运行任务的CPU
xTaskCreatePinnedToCore(
Task2
, "Task2"
, 1024 // Stack size
, NULL
, 2 // Priority
, NULL
, ARDUINO_RUNNING_CORE);//
}
void loop()
{
;
}
/*---------------------- Tasks ---------------------*/
void Task1(void *pvParameters) // This is a task.
{
tft.fillScreen(TFT_WHITE);
(void) pvParameters;
for (;;) // A Task shall never return or exit.
{
}
}
void Task2(void *pvParameters) // This is a task.
{
(void) pvParameters;
for (;;) // A Task shall never return or exit.
{
tft.fillScreen(TFT_WHITE);
/* 引入图片,相当于extern */
LV_IMG_DECLARE(cat);
lv_obj_t *image1 = lv_img_create(lv_scr_act(),NULL);/* 创建image 控件*/
lv_img_set_src(image1,&cat); /* 为控件设置图片*/
lv_obj_align(image1,NULL,LV_ALIGN_IN_TOP_MID,0,0);/* 设置控件的对齐方式,相对坐标*/
vTaskDelay(1000); // one tick delay (15ms) in between reads for stability
tft.fillScreen(TFT_WHITE);
LV_IMG_DECLARE(love);
lv_img_set_src(image1,&love); /* 为控件设置图片*/
lv_obj_align(image1,NULL,LV_ALIGN_IN_TOP_MID,0,0);/* 设置控件的对齐方式,相对坐标*/
vTaskDelay(1000); // one tick delay (15ms) in between reads for stability
lv_obj_del(image1);
tft.fillScreen(TFT_WHITE);
lv_obj_t *scr = lv_disp_get_scr_act(NULL); /* 获取当前屏幕 */
lv_obj_t *label1 = lv_label_create(scr,NULL); /* 创建 label 控件 */
lv_label_set_recolor(label1, true); /* 允许文字重新着色 */
lv_label_set_text(label1,"#000080 Moonbeam ##6666ff Moonbeam "); /* 设置文字 空格一定要有 */
lv_obj_align(label1,NULL,LV_ALIGN_IN_TOP_MID,0,0); /* 设置位置 */
vTaskDelay(1000);
lv_obj_del(label1);
tft.fillScreen(TFT_WHITE);
lv_obj_t *label2 = lv_label_create(scr,NULL); /* 创建 label 控件 */
lv_label_set_text(label2,"abcdefghijklmnopqrstabsjdjnvjjchchccbc,"); /* 设置长文本 不支持汉字 */
lv_label_set_long_mode(label2,LV_LABEL_LONG_SROLL_CIRC); /* 设置长文本的模式为循环滚动显示 */
lv_obj_set_width(label2, 240);
//lv_obj_align(label2, NULL, LV_ALIGN_CENTER, 0, 10); /* 设置位置 */
vTaskDelay(3000);
lv_obj_del(label2);
tft.fillScreen(TFT_WHITE);
/* 引入字体,等同于 extern */
LV_FONT_DECLARE(gb2312_puhui32);
static lv_style_t style_cn; /* 定义新的样式,请注意使用 static 或者全局变量 */
lv_style_init(&style_cn);
lv_style_set_text_font(&style_cn, LV_STATE_DEFAULT, &gb2312_puhui32);
//汉字显示
lv_obj_t *label3 = lv_label_create(scr,NULL); /* 创建 label 控件 */
//lv_obj_set_style(label3,&style_cn); /* 为控件设置新的 style */
lv_obj_add_style(label3, LV_LABEL_PART_MAIN, &style_cn);
lv_obj_set_pos(label3,0,0); /* 设置控件的坐标 */
lv_label_set_text(label3,"小先生"); /* 设置文字 */
lv_obj_align(label3,NULL,LV_ALIGN_IN_TOP_MID,0,100); /* 设置控件的对齐方式-相对坐标 */
vTaskDelay(1000);
lv_obj_del(label2);
tft.fillScreen(TFT_WHITE);
/*Create an array for the points of the line*/
static lv_point_t line_points[] = { {5, 5}, {5, 70}, {70, 180}, {180, 60}, {240, 10} };
/*Create style*/
static lv_style_t style_line;
lv_style_init(&style_line);
lv_style_set_line_width(&style_line, LV_STATE_DEFAULT, 8);
lv_style_set_line_color(&style_line, LV_STATE_DEFAULT, LV_COLOR_BLUE);
lv_style_set_line_rounded(&style_line, LV_STATE_DEFAULT, true);
/*Create a line and apply the new style*/
lv_obj_t * line1;
line1 = lv_line_create(lv_scr_act(), NULL);
lv_line_set_points(line1, line_points, 5); /*Set the points*/
lv_obj_add_style(line1, LV_LINE_PART_MAIN, &style_line); /*Set the points*/
lv_obj_align(line1, NULL, LV_ALIGN_CENTER, 0, 0);
vTaskDelay(3000);
lv_obj_del(line1);
tft.fillScreen(TFT_WHITE);
/*Create a LED and switch it OFF*/
lv_obj_t * led1 = lv_led_create(lv_scr_act(), NULL);
lv_obj_align(led1, NULL, LV_ALIGN_CENTER, -80, 0);
lv_led_off(led1);
/*Copy the previous LED and set a brightness*/
lv_obj_t * led2 = lv_led_create(lv_scr_act(), led1);
lv_obj_align(led2, NULL, LV_ALIGN_CENTER, 0, 0);
lv_led_set_bright(led2, 190);
/*Copy the previous LED and switch it ON*/
lv_obj_t * led3 = lv_led_create(lv_scr_act(), led1);
lv_obj_align(led3, NULL, LV_ALIGN_CENTER, 80, 0);
lv_led_on(led3);
vTaskDelay(2000);
lv_obj_del(led1);
lv_obj_del(led2);
lv_obj_del(led3);
}
}
ESP32 使用LVGL 带FreeRTOS