LVGL (Light and Versatile Graphics Library) 是一個(gè)很流行的免費(fèi)開源嵌入式圖形庫(kù),可以幫助很多 MCU、MPU 和顯示類型創(chuàng)建好看的 UI。它由Gábor Kiss-Vámosi于2016年創(chuàng)建的一個(gè)開源項(xiàng)目,目前由來(lái)自世界各地的志愿者共同維護(hù)和開發(fā)。LVGL是用C語(yǔ)言編寫的,遵循MIT協(xié)議,可以自由地使用和修改。LVGL支持多種操作系統(tǒng),例如Linux、Windows、RTOS等,也可以在開發(fā)板上運(yùn)行。LVGL還支持多種顯示器驅(qū)動(dòng)器和觸摸屏驅(qū)動(dòng)器,可以與不同大小和分辨率的顯示器兼容。
LVGL還提供了多種語(yǔ)言的綁定,例如Python、Micropython、JavaScript等,以及多種開發(fā)工具,例如模擬器、視覺(jué)化設(shè)計(jì)器、字體轉(zhuǎn)換器等。
1 硬件設(shè)計(jì)
ESP32參數(shù)如下:
參數(shù) | 值 |
---|---|
型號(hào) | esp32-s2 |
flash | 8M |
psram | 2M |
LCD屏幕參數(shù)如下:
參數(shù) | 值 |
---|---|
尺寸 | 2.6寸 |
分辨率 | 240*240 |
接口 | spi |
驅(qū)動(dòng)IC | ST7789V |
觸摸 | 無(wú) |
ESP32與LCD的接線如下:
esp32-s2 | lcd | 說(shuō)明 |
---|---|---|
VCC | VCC | 電源正 |
GND | GND | 電源負(fù) |
GPIO36SPI_CLK | CLK | SPI時(shí)鐘線 |
GPIO35SPI_MOSI | SDI | SPI數(shù)據(jù)線,esp32輸出,lcd輸入 |
GPIO37SPI_MISO | SDO | SPI數(shù)據(jù)線,esp32輸入,lcd輸出,注:該引腳一般可以不用,除非你要讀取LCD的信息 |
GPIO34 | CS | SPI片選 |
GPIO4 | WR(D/C) | 并口時(shí)作為寫入信號(hào)/SPI時(shí)作為命令或參數(shù)的選擇 |
GPIO1 | RST | LCD復(fù)位引腳,可以用普通IO控制,引腳不足的情況下也可以和esp32的復(fù)位引腳接到一起 |
GPIO2 | BL | 背光引腳 |
2 軟件設(shè)計(jì)
注:本文是基于PlatformIO搭建的Arduino環(huán)境,所有軟件代碼也是基于這個(gè)環(huán)境編譯。不知道怎么搭建的同學(xué)自行查閱資料,這個(gè)網(wǎng)上教程有很多的。
2.1 新建PlatformIO工程
輸入項(xiàng)目名稱,選擇芯片及開發(fā)板,選擇arduino架構(gòu)。
配置項(xiàng)目參數(shù)(platformio.ini)。
注:具體的項(xiàng)目參數(shù)根據(jù)自己的實(shí)際情況配置。更多的參數(shù)詳情及使用方法,可以在下面的鏈接查看。
https://docs.platformio.org/page/projectconf.html
https://docs.platformio.org/en/latest/platforms/espressif32.html
本文配置的參數(shù)如下:
[env:adafruit_metro_esp32s2]
platform = espressif32
board = adafruit_metro_esp32s2
framework = arduino
lib_deps =
bodmer/TFT_eSPI@^2.5.43
lvgl/lvgl@^8.3.11
monitor_speed = 115200
board_build.f_cpu = 240000000L
board_build.f_flash = 80000000L
board_build.flash_mode = qio
board_upload.flash_size = 8MB
board_upload.speed = 921600
board_upload.use_1200bps_touch = true
board_upload.wait_for_upload_port = true
build_flags =
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_DFU_ON_BOOT=0
-DARDUINO_USB_MSC_ON_BOOT=0
-DCORE_DEBUG_LEVEL=0
lib_ldf_mode = deep+
2.2 添加開源庫(kù)
在PIO Home界面選擇Libraries,搜索需要的庫(kù)并添加到自己的工程目錄下。
注:每個(gè)PlatformIO工程的庫(kù)都是獨(dú)立的,所以你這里要把庫(kù)添加到對(duì)應(yīng)的工程,舊工程下載的庫(kù)并不能直接用于新工程上。
本文需要使用的Arduino庫(kù)如下:
Arduino庫(kù) | 版本 | 說(shuō)明 |
---|---|---|
TFT_eSPI | 2.5.43 | 該庫(kù)通過(guò)SPI方式驅(qū)動(dòng)LCD,支持多種LCD常用驅(qū)動(dòng)IC |
lvgl | 8.3.11 | lvgl庫(kù),方便做各種圖形處理 |
配置流程大致如下:
添加成功后可以在工程目錄下看到已添加的庫(kù),platformio.ini文件中也會(huì)自動(dòng)生成依賴庫(kù)代碼。
2.3 配置LCD底層接口
2.3.1 LCD接口配置
TFT_eSPI
庫(kù)集成了很多常用LCD驅(qū)動(dòng)IC的接口,根據(jù)自己使用的LCD屏幕參數(shù)修改User_Setup.h
文件,即可調(diào)用底層的接口函數(shù)。
注:User_Setup.h定義的參數(shù)比較多,我這里就不一個(gè)個(gè)細(xì)說(shuō)了。我主要講幾個(gè)重要的參數(shù)。
1)驅(qū)動(dòng)IC
根據(jù)自己使用的LCD驅(qū)動(dòng)IC打開對(duì)應(yīng)的宏。注意這些驅(qū)動(dòng)只能選擇一個(gè)打開,不用的要注釋掉。
2)RGB數(shù)據(jù)格式
RGB格式指的是像素點(diǎn)顏色數(shù)據(jù)的排列方式,一般就BGR和RGB兩種,區(qū)別其實(shí)就是數(shù)據(jù)高位在前還是低位在前,這個(gè)主要是用于圖片顯示,要用哪種格式主要是看你要顯示的內(nèi)容是怎么排的,不確定的話可以先不改,調(diào)試的時(shí)候如果顏色不對(duì)的話再換過(guò)來(lái)就好了。
3)像素
根據(jù)自己屏幕的像素修改,也可以先不改,直接在后面應(yīng)用的時(shí)候再改。
4)GPIO
根據(jù)自己的電路設(shè)置引腳,除了幾個(gè)必要的引腳,有些引腳可以不配置。
如:RST可以通過(guò)硬件和MCU的RST接到一起,軟件配置成-1即可。BL背光也可以硬件直接控制。
注意:相同的GPIO定義只能打開一個(gè),默認(rèn)有些打開了的要注釋掉。
特別說(shuō)明:建議使用2.4.0以上版本,因?yàn)橹暗陌姹娟P(guān)于ESP32的引腳定義是分成HSPI和VSPI的,默認(rèn)使用VSPI,如果要用HSPI要打開USE_HSPI_PORT定義,但是這套框架兼容性不強(qiáng),不適用于ESP32-S2和ESP32-C2,而2.4.0以上版本取消了這個(gè)定義,直接都按引腳號(hào)來(lái)定義,這樣一來(lái)就不用區(qū)分HSPI和VSPI了。
5)字庫(kù)
TFT_eSPI自帶的這些字庫(kù)你可以直接用,如果你有自己的字庫(kù)不用這里的話也可以注釋掉。flash空間足夠的情況下,這點(diǎn)代碼加不加都沒(méi)關(guān)系。
6)SPI通訊速率
SPI通訊速率一般默認(rèn)即可,默認(rèn)的這個(gè)速率一般是足夠了的,如果需要更快的話可以自己修改。
7)ESP32的特殊定義
TFT_eSPI舊版本關(guān)于ESP32的SPI接口是分為HSPI和VSPI兩種的,默認(rèn)使用VSPI,如果要用HSPI要打開USE_HSPI_PORT定義,如果你只是用ESP32,那這個(gè)框架是沒(méi)什么問(wèn)題的。
但是我之前因?yàn)轫?xiàng)目需要從ESP32改用ESP32-S2,結(jié)果發(fā)現(xiàn)ESP32-S2就沒(méi)有HSPI和VSPI,所有接口都是FSPI,于是我就要把底層很多東西都改掉才能正常使用。
不過(guò)現(xiàn)在TFT_eSPI庫(kù)2.4.0以上版本就已經(jīng)把這個(gè)問(wèn)題改掉了,兼容了ESP32-S2和ESP-C3,取消了USE_HSPI_PORT這個(gè)定義,SPI接口都以GPIO引腳號(hào)來(lái)定義。
所以,我建議都用新版本的庫(kù)吧,兼容性更好,也不用去考慮用HSPI還是VSPI。
2.3.2 LCD驅(qū)動(dòng)測(cè)試
TFT_eSPI庫(kù)配置好了之后可以先燒錄一個(gè)簡(jiǎn)單的程序,驗(yàn)證一下硬件接線和代碼是否能正常運(yùn)行。當(dāng)然,到了后面把lvgl配置好再一步到位也行。
注意:如果此時(shí)已經(jīng)把lvgl庫(kù)加入到工程,又沒(méi)有配置的話,編譯會(huì)出錯(cuò)(找不到lv_conf.h文件),得把lvgl庫(kù)刪除或者配置好lvgl庫(kù)再測(cè)試。
測(cè)試代碼如下:
#include <Arduino.h>
#include <SPI.h>
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
void setup()
{
Serial.begin(115200);
tft.begin();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
}
void loop()
{
tft.fillScreen(TFT_RED);
delay(1000);
tft.fillScreen(TFT_GREEN);
delay(1000);
tft.fillScreen(TFT_BLUE);
delay(1000);
}
測(cè)試結(jié)果:
如果能正常運(yùn)行的話,屏幕每隔1s會(huì)依次點(diǎn)亮紅綠藍(lán)三種顏色。
2.4 配置lvgl
2.4.1 添加配置文件
復(fù)制lv_conf_template.h
到同一路徑下,并改名為lvgl_conf.h
。否則編譯會(huì)報(bào)錯(cuò)(找不到lv_conf.h文件)。
2.4.2 修改lvgl配置
lvgl里面的lvgl_conf.h
文件有很多配置參數(shù),具體的我就不一一描述了。需要用到哪些配置,網(wǎng)上都能找到詳細(xì)的教程。我這里講幾個(gè)必須要配置的東西。
1)把下面的宏打開。
2)啟用自定義時(shí)鐘
注意:沒(méi)有一步的話會(huì)導(dǎo)致屏幕停留在第一幀的界面。
3)配置lvgl的顯示接口
要把底層的LCD顯示接口對(duì)接到lvgl這套框架里面,這樣lvgl才能控制顯示屏。
參考代碼:
/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp_drv, 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( ( uint16_t * )&color_p->full, w * h, true );
tft.endWrite();
lv_disp_flush_ready( disp_drv );
}
4)配置lvgl的觸摸接口
注:因?yàn)槲疫@里屏幕沒(méi)有觸摸接口,所以我這里把代碼注釋掉了,如果要用觸摸接口的話,這里需要打開。
參考代碼:
/*Read the touchpad*/
void my_touchpad_read( lv_indev_drv_t * indev_drv, lv_indev_data_t * data )
{
// uint16_t touchX, touchY;
// bool touched = tft.getTouch( &touchX, &touchY, 600 );
// if( !touched )
// {
// data->state = LV_INDEV_STATE_REL;
// }
// else
// {
// data->state = LV_INDEV_STATE_PR;
// /*Set the coordinates*/
// data->point.x = touchX;
// data->point.y = touchY;
// Serial.print( "Data x " );
// Serial.println( touchX );
// Serial.print( "Data y " );
// Serial.println( touchY );
// }
}
2.4.3 lvgl代碼測(cè)試
示例代碼:
#include <Arduino.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include "lvgl.h"
TFT_eSPI tft = TFT_eSPI();
static lv_disp_draw_buf_t draw_buf;
/* Display flushing */
void my_disp_flush(lv_disp_drv_t *disp_drv, 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( ( uint16_t * )&color_p->full, w * h, true );
tft.endWrite();
lv_disp_flush_ready( disp_drv );
}
/*Read the touchpad*/
void my_touchpad_read( lv_indev_drv_t * indev_drv, lv_indev_data_t * data )
{
// uint16_t touchX, touchY;
// bool touched = tft.getTouch( &touchX, &touchY, 600 );
// if( !touched )
// {
// data->state = LV_INDEV_STATE_REL;
// }
// else
// {
// data->state = LV_INDEV_STATE_PR;
// /*Set the coordinates*/
// data->point.x = touchX;
// data->point.y = touchY;
// Serial.print( "Data x " );
// Serial.println( touchX );
// Serial.print( "Data y " );
// Serial.println( touchY );
// }
}
void lvgl_user_init(void)
{
lv_init();
/*Set the touchscreen calibration data,
the actual data for your display can be acquired using
the Generic -> Touch_calibrate example from the TFT_eSPI library*/
// uint16_t calData[5] = { 275, 3620, 264, 3532, 1 };
// tft.setTouch( calData );
lv_color_t* buf1 = (lv_color_t*) heap_caps_malloc(240 * 240, MALLOC_CAP_SPIRAM);
// lv_color_t* buf2 = (lv_color_t*) heap_caps_malloc(240 * 240, MALLOC_CAP_SPIRAM);
lv_disp_draw_buf_init( &draw_buf, buf1, NULL, 240 * 240);
/*Initialize the display*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init( &disp_drv );
/*Change the following line to your display resolution*/
disp_drv.hor_res = 240;
disp_drv.ver_res = 240;
disp_drv.flush_cb = my_disp_flush;
disp_drv.full_refresh = 1;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
/*Initialize the (dummy) input device driver*/
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;
lv_indev_drv_register(&indev_drv);
/*Get lvgl version*/
String LVGL_Arduino = "Hello LVGL! ";
LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch(); // version
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, LVGL_Arduino.c_str());
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); //display
}
void setup()
{
Serial.begin(115200);
tft.begin();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
lvgl_user_init();
}
void loop()
{
lv_timer_handler(); /* let the GUI do its work */
delay(5);
}
測(cè)試結(jié)果:
代碼正常運(yùn)行,調(diào)用lvgl的文本控件,打印lvgl的版本號(hào)。
結(jié)束語(yǔ)
我這里只是舉了一個(gè)簡(jiǎn)單的例子講解如何搭建lvgl的環(huán)境,實(shí)際上lvgl還有很多的控件和功能,也有一些demo可以運(yùn)行測(cè)試,這些就留給你們繼續(xù)探索了。如果還有什么問(wèn)題,歡迎在評(píng)論區(qū)留言。
完整工程源碼下載鏈接
想了解更多Arduino的內(nèi)容,可以關(guān)注一下博主,后續(xù)我還會(huì)繼續(xù)分享更多的經(jīng)驗(yàn)給大家。
基于Arduino的開發(fā)教程匯總:
https://blog.csdn.net/ShenZhen_zixian/article/details/121659482
如果這篇文章能夠幫到你,就…你懂得。