【ESP32】Arduino+LVGL 如何使用PSRAM优化显示

前言

最近项目需要买了一个ESP-WROVER-E模块,这个模块内置了一个4MB的PSRAM,对于需要大内存运行的场景非常友好。

在这里插入图片描述
在这里插入图片描述
在我的项目中用到了LVGL这个第三方图形库,下面就讲一下如何最大化地在LVGL中使用这个PSRAM。

教程

在lv_conf.h文件中,找到下面的代码。

/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
#define LV_MEM_CUSTOM 1
#if LV_MEM_CUSTOM == 0
    /*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
    #define LV_MEM_SIZE (48U * 1024U)          /*[bytes]*/

    /*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
    #define LV_MEM_ADR 0     /*0: unused*/
    /*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/
    #if LV_MEM_ADR == 0
        #undef LV_MEM_POOL_INCLUDE
        #undef LV_MEM_POOL_ALLOC
    #endif
    
#else       /*LV_MEM_CUSTOM*/
    #define LV_MEM_CUSTOM_INCLUDE <stdlib.h>   /*Header for the dynamic memory function*/
    #define LV_MEM_CUSTOM_ALLOC   malloc
    #define LV_MEM_CUSTOM_FREE    free
    #define LV_MEM_CUSTOM_REALLOC realloc
#endif     /*LV_MEM_CUSTOM*/

默认情况下LV_MEM_CUSTOM为0,这时LVGL会自己弄一个内存池进行管理,这个内存池是使用ESP32的SRAM,而ESP32能用的SRAM实际大小也就320KB。
如果ESP32上自带PSRAM的话,可以将LV_MEM_CUSTOM设为1,这时LVGL将使用stdlib.h头文件内的函数分配内存,而使用这些函数分配内存是可以充分使用到SRAM和PSRAM的内存的

接下来修改与显示有关的代码。官方会提供3个显示刷新的方案——单缓冲10行刷新、双缓冲10行刷新和双缓冲全像素刷新。如果我们的ESP32上自带PSRAM,那么肯定尽可能选择刷新速度最快的双缓冲全像素刷新方式,但在用之前函数最好还是计算一下内存的占用,大一点的屏幕在该模式下要占满4MB的内存也不是不可能的。

void Display::init(void)
{
    display_init();

    // static lv_disp_draw_buf_t draw_buf_dsc_1;
    // static lv_color_t buf_1[MY_DISP_HOR_RES * 10];                          /*A buffer for 10 rows*/
    // lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/

    // static lv_disp_draw_buf_t draw_buf_dsc_2;
    // static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10];                        /*A buffer for 10 rows*/
    // static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10];                        /*An other buffer for 10 rows*/
    // lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/

    static lv_disp_draw_buf_t draw_buf_dsc_3;
    // static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*A screen sized buffer*/
    // static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*Another screen sized buffer*/
    lv_color_t* buf_3_1 = (lv_color_t*) heap_caps_malloc(MY_DISP_HOR_RES * MY_DISP_VER_RES, MALLOC_CAP_SPIRAM);
    lv_color_t* buf_3_2 = (lv_color_t*) heap_caps_malloc(MY_DISP_HOR_RES * MY_DISP_VER_RES, MALLOC_CAP_SPIRAM);
    lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, MY_DISP_VER_RES * MY_DISP_HOR_RES);   /*Initialize the display buffer*/
    static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = MY_DISP_HOR_RES;
    disp_drv.ver_res = MY_DISP_VER_RES;
    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = my_disp_flush;
    disp_drv.full_refresh = 1;
    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_3;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

在开辟屏幕的2个缓冲区时,我并没有使用stdlib.h头文件中的malloc函数,而是使用esp_heap_caps.h头文件中的heap_caps_malloc函数,因为该函数可以决定内存空间开辟的方式,第二个参数填入MALLOC_CAP_SPIRAM,这样该空间会被强制开辟在PSRAM中。
如果我们用了双缓冲全像素刷新,记得在注册显示设备时加上下面的代码。

disp_drv.full_refresh = 1;

这样是告诉LVGL我们的程序可以进行全像素刷新。

接下来还可以进行一些额外的设置,我们可以在lv_conf.h文件找到下面的代码。

/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 20      /*[ms]*/

/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 10     /*[ms]*/

第一个设置的是屏幕的刷新频率,默认为30ms刷新一次,我设置到了20ms刷新一次,那么最高就可以有50fps的帧率。第二个是设置输入设备的采样频率,默认为20ms采样一次,我设置到了10ms,这样可以尽可能提高触摸的灵敏度。

总结

可能有同学在设置了上面的参数后仍然发现屏幕好像还是卡卡的,这是因为以上都是在软件层面进行的优化,软件的优化是有限的,不如直接在硬件上进行优化。
就比如,我使用的屏幕虽然只有240X240的分辨率,但它使用的是SPI协议进行通信,相比起使用8080并口的显示屏,刷新速度肯定是大打折扣的;前者每次传输1bit,但后者每次可以传输8bit或16bit,这个差距是巨大的。
又比如,PSRAM虽然有4MB的空间,看上去很大,但它走的也是SPI协议;而我们平时用的SRAM或SDRAM都是使用好几个管脚进行寻址和传数据,速度上面没得比。但PSRAM的好处就是空间大,SDRAM、SRAM的空间一般只能做几百KB。

  • 5
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
可以尝试使用以下步骤来实现: 1. 使用Arduino IDE和ESP32开发板,引入lvglESP32网络库。 2. 创建一个基础屏幕,包括日期、时间和天气。 3. 使用Open Weather Map等天气API从互联网上获取天气数据。 4. 更新屏幕上的天气信息,如温度、天气状况等。 5. 添加网络连接,以确保每日更新天气预报。 完整代码如下: #include <WiFi.h> #include "time.h" #include "lvgl/lvgl.h" #include "esp32_digital_clock.h" #include "esp32_temperature_sensor.h" #define WIFI_SSID "YOUR_SSID_NAME_HERE" #define WIFI_PASS "YOUR_WIFI_PASSWORD_HERE" // Open Weather Map API key #define API_KEY "YOUR_API_KEY_HERE" //Server details const char* server = "api.openweathermap.org"; const String url = "/data/2.5/weather?q=YOUR_CITY_NAME_HERE&appid=" + String(API_KEY); // Initialize the time library void initTime() { configTime(0, 0, "pool.ntp.org"); while (!time(nullptr)) { delay(1000); } Serial.println("Time updated."); } // Initialize the network library void initNetwork() { WiFi.begin(WIFI_SSID, WIFI_PASS); Serial.print("Connecting to Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); Serial.print("Connected to Wi-Fi: "); Serial.println(WiFi.localIP()); } // Fetch weather data String getWeatherData(const char* url) { WiFiClient client; if (!client.connect(server, 80)) { return ""; } client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + server + "\r\n" + "Connection: close\r\n\r\n"); while (!client.available()) { delay(50); } String line = client.readStringUntil('\r'); while (client.available()) { line += client.readStringUntil('\r'); } return line; } // Parse weather data void displayWeather() { const char* url = String("/data/2.5/weather?q=YOUR_CITY_NAME_HERE&appid=" + String(API_KEY)).c_str(); String data = getWeatherData(url); if (data == "") { Serial.println("Error fetching data from URL."); return; } Serial.println(data); temperature_t temperature = esp32_temperature_get(); if (temperature.success) { Serial.print("Temperature: "); Serial.println(temperature.temperature_celsius); } } void setup() { Serial.begin(115200); // Initialize the time library initTime(); // Initialize the network library initNetwork(); // Initialize LVGL library lv_init(); esp32_digital_clock_create(); esp32_temperature_sensor_create(); // Display weather data on screen displayWeather(); } void loop() { lv_task_handler(); delay(100); } 再次提醒,这个问题是编程类问题,可以回答。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马浩同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值