esp-idf 移植 lvgl8.3.3

2022-10-08 18:01:27

一、準備材料

  1. 開發板:esp32s3
  2. idf版本:4.4.2
  3. lvgl:8.3.3

注意:lvgl不要選擇master分支,編譯失敗時不好確定問題。

二、建立idf專案

方式一

通過 VSCode 建立專案

  1. 在命令面板中搜尋 esp-idf new,開始建立專案

  2. 專案設定資訊

  3. 選擇需要的模板,也可以選擇想要的案例

注意: 需要VSCode 中安裝 idf 環境的可以看我之前的筆記VSCode 中安裝 esp-idf

方式二

通過命令建立專案

idf.py create-project

方式三

直接在路徑 %ESP-IDF%\espressif\frameworks\esp-idf-v4.4.2\examples 中拷貝自己需要的使用案例

三、新增LVGL庫

  1. 下載lvgl8.3.3
    GitHub:https://github.com/lvgl/lvgl.git

  2. 將lvgl庫新增到專案的 components 檔案中,如下圖所示

    ** 注意,如果覺得檔案比較多的話,可以刪除不用的檔案,如下圖所示:

  3. lv_conf_template.h 重新命名為 lv_conf_.h ,並將檔案中的 #if 0 改為 #if 1

  4. 設定開發板為esp32s3

  5. 編譯

    ** 注意:**如果出現編譯錯誤時,檢查一下自己是否下載成了master分支

四、新增顯示驅動

  1. 下載lvgl_esp32_drivers
    GitHub:https://github.com/lvgl/lvgl_esp32_drivers

  2. 使用命令 idf.py menuconfig 開啟圖形設定介面

  3. 進入 Component config → LVGL ESP Drivers → LVGL TFT Display controller 設定顯示驅動資訊

  4. SPI引腳設定

  5. 進入 Component config → LVGL configguration 設定LVGL資訊

  6. 按s鍵進行儲存,完成後按Q退出

  7. 設定螢幕資訊和SPI通道數量
    編譯後會產生以下錯誤,如果所示
    未定義顯示器的畫素寬度和高度

    未定義開發板 SPI 的通道數量

    只需要在檔案 lvgl_helpers.h 中新增以下定義即可,如圖所示

    #define SPI_HOST_MAX 3                  // 開發板 SPI 通道數量
    #define LV_HOR_RES_MAX 240              // 顯示器水平畫素
    #define LV_VER_RES_MAX 320              // 顯示器垂直畫素
    

  8. 編譯通過後,在 main.c 檔案中新增程式,程式後面附上

  9. SPI 通道錯誤

    執行後不斷重啟,並出現圖示中的問題,值需要在檔案 `` 中將SPI通道改為自動模式 SPI_DMA_CH_AUTO 即可,如下圖所示:

到此螢幕就能正常顯示了。

五、新增觸控驅動

  1. 使用命令 idf.py menuconfig 開啟圖形設定介面

  2. 進入 Component config → LVGL ESP Drivers → LVGL Touch controller 開啟觸控驅動

  3. 返回上一級,選擇I2C通道

  4. 進入 Component config → I2C Port Settings 設定I2C引腳

六、main.c檔案

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_freertos_hooks.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "driver/gpio.h"

/* Littlevgl specific */
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif

#include "lvgl_helpers.h"

/*********************
 *      DEFINES
 *********************/
#define TAG "main"
#define LV_TICK_PERIOD_MS 1

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void lv_tick_task(void *arg);
static void guiTask(void *pvParameter);
static void create_demo_application(void);

/**********************
 *   APPLICATION MAIN
 **********************/
void app_main() {

    /* 使用任務建立圖形時,必須使用固定任務建立
     * 注意:不使用Wi-Fi或藍芽時,可以將gui任務固定到核心0 */
    xTaskCreatePinnedToCore(guiTask, "gui", 4096*2, NULL, 0, NULL, 1);
}

/* Creates a semaphore to handle concurrent call to lvgl stuff
 * If you wish to call *any* lvgl function from other threads/tasks
 * you should lock on the very same semaphore! */
SemaphoreHandle_t xGuiSemaphore;

static void guiTask(void *pvParameter) {

    (void) pvParameter;
    xGuiSemaphore = xSemaphoreCreateMutex();

    lv_init();

    /* 初始化驅動程式使用的SPI或I2C匯流排 */
    lvgl_driver_init();

    /* 建立堆記憶體,用過圖形緩衝區buf1 */
    lv_color_t* buf1 = heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
    assert(buf1 != NULL);

    /* 不使用單色顯示器時使用雙緩衝 */
#ifndef CONFIG_LV_TFT_DISPLAY_MONOCHROME
    lv_color_t* buf2 = heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
    assert(buf2 != NULL);
#else
    static lv_color_t *buf2 = NULL;
#endif

    static lv_disp_draw_buf_t disp_buf;

    uint32_t size_in_px = DISP_BUF_SIZE;

#if defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820         \
    || defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_JD79653A    \
    || defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_UC8151D     \
    || defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1306

    /* Actual size in pixels, not bytes. */
    size_in_px *= 8;
#endif

    /* Initialize the working buffer depending on the selected display.
     * NOTE: buf2 == NULL when using monochrome displays. */
    lv_disp_draw_buf_init(&disp_buf, buf1, buf2, size_in_px);

    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.flush_cb = disp_driver_flush;

    /* When using a monochrome display we need to register the callbacks:
     * - rounder_cb
     * - set_px_cb */
#ifdef CONFIG_LV_TFT_DISPLAY_MONOCHROME
    disp_drv.rounder_cb = disp_driver_rounder;
    disp_drv.set_px_cb = disp_driver_set_px;
#endif

    disp_drv.draw_buf = &disp_buf;
    lv_disp_drv_register(&disp_drv);

    /* Register an input device when enabled on the menuconfig */
#if CONFIG_LV_TOUCH_CONTROLLER != TOUCH_CONTROLLER_NONE
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.read_cb = touch_driver_read;
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    lv_indev_drv_register(&indev_drv);
#endif

    /* Create and start a periodic timer interrupt to call lv_tick_inc */
    const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lv_tick_task,
        .name = "periodic_gui"
    };
    esp_timer_handle_t periodic_timer;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LV_TICK_PERIOD_MS * 1000));

    /* Create the demo application */
    create_demo_application();

    while (1) {
        /* Delay 1 tick (assumes FreeRTOS tick is 10ms */
        vTaskDelay(pdMS_TO_TICKS(10));

        /* Try to take the semaphore, call lvgl related function on success */
        if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY)) {
            lv_task_handler();
            xSemaphoreGive(xGuiSemaphore);
       }
    }

    /* A task should NEVER return */
    free(buf1);
#ifndef CONFIG_LV_TFT_DISPLAY_MONOCHROME
    free(buf2);
#endif
    vTaskDelete(NULL);
}

/**
 * @brief 建立標籤
 */
void lvgl_lable_test(){
    /* 建立一個標籤 */
    lv_obj_t* label = lv_label_create(lv_scr_act());
    if (NULL != label)
    {
        // lv_obj_set_x(label, 90);                         // 設定控制元件的X座標
        // lv_obj_set_y(label, 100);                        // 設定控制元件的Y座標
        // lv_obj_set_size(label, 60, 20);                  // 設定控制元件大小
        lv_label_set_text(label, "Counter");                // 初始顯示 0
        // lv_obj_center(label);                            // 居中顯示
        lv_obj_align(label, LV_ALIGN_CENTER, 0, -50);       // 居中顯示後,向上偏移50
    }
}

/**
 * @brief 按鈕事件回撥函數
 */
static void btn_event_callback(lv_event_t* event)
{
    static uint32_t counter = 1;
 
    lv_obj_t* btn = lv_event_get_target(event);                 //獲取事件物件
    if (btn != NULL)
    {
        lv_obj_t* btn_parent =  lv_obj_get_parent(btn);
        lv_obj_t* label = lv_obj_get_child(btn_parent, 0);
        lv_label_set_text_fmt(label, "%d", counter);            //設定顯示內容
        lv_obj_align(label, LV_ALIGN_CENTER, 0, -50);           // 居中顯示後,向上偏移50
        counter++;
    }
}

/**
 * @brief 建立按鈕
 */
void lvgl_button_test(){
    /* 在當前介面中建立一個按鈕 */
    lv_obj_t* btn = lv_btn_create(lv_scr_act());                                        // 建立Button物件
    if (btn != NULL)
    {
        lv_obj_set_size(btn, 80, 20);                                                   // 設定物件寬度和高度
        // lv_obj_set_pos(btn, 90, 200);                                                // 設定按鈕的X和Y座標
        lv_obj_add_event_cb(btn, btn_event_callback, LV_EVENT_CLICKED, NULL);           // 給物件新增CLICK事件和事件處理回撥函數
        lv_obj_align(btn, LV_ALIGN_CENTER, 0, 50);                                      // 居中顯示後,向下偏移50
 
        lv_obj_t* btn_label = lv_label_create(btn);                                     // 基於Button物件建立Label物件
        if (btn_label != NULL)
        {
            lv_label_set_text(btn_label, "button");                                     // 設定顯示內容
            lv_obj_center(btn_label);                                                   // 物件居中顯示
        }
    }    
}

static void create_demo_application(void)
{
    /* 載入標籤 */
    lvgl_lable_test();
    /* 載入按鈕 */
    lvgl_button_test();

}

static void lv_tick_task(void *arg) {
    (void) arg;
       
    lv_tick_inc(LV_TICK_PERIOD_MS);
}