STM32 + RT-Thread + LVGL

浇筑菜鸟發表於2024-06-11

一、基本資訊

  • MCU:STM32F103ZET6
  • RT-Thread:5.0.2
  • LVGL:8.3.11
  • LCD:ST7735s
  • 編譯環境:RTThread studio

二、LVGL 移植要求

  • 16、32或64位微控制器或處理器
  • 建議速度大於16 MHz
  • 快閃記憶體/ROM: > 64 kB(建議180 kB)
  • 記憶體:8 kB(建議24 kB)
  • 1個幀緩衝器:在MCU、外部RAM或顯示控制器中
  • LVGL的圖形緩衝:>“水平解析度”畫素(推薦1/10“螢幕尺寸”)
  • C99或更新的編譯器
  • 基本的C(或C++)知識:指標、結構、回撥

三、新增 LVGL 軟體包

  1. 新增軟體包

  2. LVGL 檔案目錄

  3. lv_rt_thread_port.c 檔案

    由上圖可知:檔案已經幫我們完成了三個函式的呼叫,只需要在對函式進行例項即可,由於我沒用到觸控式螢幕,所以將 lv_port_indev_init() 的呼叫遮蔽了

四、lv_user_gui_init() 函式

此函式的主要作用是 LVGL 啟動的初始化介面,相當於開機介面,主要是消除初始化啟動功能時導致螢幕出現長時間的白屏的現象,程式如下

點選檢視程式碼
#include <lvgl.h>

void lv_user_gui_init(void)
{
    /* 獲取預設顯示器的活動螢幕 */
    lv_obj_t *scr = lv_scr_act();
    lv_obj_clean(scr); /* 清屏 */

    /* 建立介面啟動介面 */
    lv_obj_t *page = lv_obj_create(scr);
    lv_obj_set_size(page, LV_HOR_RES, LV_VER_RES);
    lv_obj_set_style_bg_color(page, lv_color_black(), LV_PART_MAIN);            /* 設定背景顏色 */
    lv_obj_set_style_radius(page, 0, LV_PART_MAIN | LV_STATE_DEFAULT);          /* 設定導角為0 */
    lv_obj_set_style_border_width(page, 0, LV_PART_MAIN | LV_STATE_DEFAULT);    /* 設定邊框為0 */

    /* 新增標籤 */
    lv_obj_t *label = lv_label_create(page);
    lv_label_set_text(label, "Loading");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}

五、lv_port_disp_init() 函式

此函式主要的作用是,初始化螢幕,並將螢幕的影像重新整理函式與 flush_cb 函式進行繫結,程式如下

點選檢視程式碼
#include <lvgl.h>
#include <rtthread.h>
#include <board.h>

//#define DRV_DEBUG
#define LOG_TAG             "LVGL.port.disp"
#include <drv_log.h>

static rt_device_t lcd_device = RT_NULL;
static struct rt_device_graphic_info lcd_info;

static lv_disp_drv_t disp_drv;  /* 顯示驅動程式的描述符 */
/* 用於儲存緩衝區的靜態或全域性變數 */
static lv_disp_draw_buf_t disp_buf;

void lv_port_disp_init(void)
{
    rt_err_t result;

   void *lv_disp_buf1 = RT_NULL;
   void *lv_disp_buf2 = RT_NULL;

   /* 查詢 LCD 裝置 */
   lcd_device = rt_device_find("lcd");
   if (lcd_device == 0)
   {
       LOG_E("lcd_device error!");
       return;
   }

   result = rt_device_open(lcd_device, RT_DEVICE_FLAG_RDWR);
   if(result != RT_EOK)
   {
       LOG_E("open lcd device failed");
       return;
   }

   /* get framebuffer address */
   result = rt_device_control(lcd_device, RTGRAPHIC_CTRL_GET_INFO, &lcd_info);
   if (result != RT_EOK)
   {
       LOG_E("error!");
       /* get device information failed */
       return;
   }

   RT_ASSERT (lcd_info.bits_per_pixel == 8 || lcd_info.bits_per_pixel == 16 ||
           lcd_info.bits_per_pixel == 24 || lcd_info.bits_per_pixel == 32);

   lv_disp_buf1 = rt_malloc(lcd_info.smem_len * sizeof(lv_color_t));
   rt_memset(lv_disp_buf1, 0, lcd_info.smem_len * sizeof(lv_color_t));
   RT_ASSERT(lv_disp_buf1 != RT_NULL);

   lv_disp_buf2 = rt_malloc(lcd_info.smem_len * sizeof(lv_color_t));
   rt_memset(lv_disp_buf2, 0, lcd_info.smem_len * sizeof(lv_color_t));
   RT_ASSERT(lv_disp_buf2 != RT_NULL);

   /* 使用緩衝區初始化 disp_buf */
   lv_disp_draw_buf_init(&disp_buf, lv_disp_buf1, lv_disp_buf2, lcd_info.smem_len);

   lv_disp_drv_init(&disp_drv);

   /* 設定顯示器的解析度 */
   disp_drv.hor_res = lcd_info.width;
   disp_drv.ver_res = lcd_info.height;

   /* 設定顯示緩衝區 */
   disp_drv.draw_buf = &disp_buf;

   /* 用於將緩衝區的內容複製到顯示器 */
   disp_drv.flush_cb = lcd_device->user_data;

   /* 註冊驅動程式 */
   lv_disp_drv_register(&disp_drv);


}

六、ST7735s 驅動程式

這裡不要侷限於 ST7735s 這個螢幕,主要是介紹 LCD 與 LVGL 對接的 bsp 的編寫過程。程式中的其他函式主要都是初始化 lcd 的工作,主要關注 lcd_fb_flush 函式,此函式會在 LVGL 中介面更新的時候呼叫,從而重新整理螢幕的顯示。

點選檢視程式碼
/**
 * @brief  LCD 驅動的操作函式
 * @param  device LCD 裝置結構體
 * @param  cmd 操作命令
 * @param  args 傳入的引數
 * @retval None
 */
static rt_err_t drv_lcd_control(struct rt_device *device, int cmd, void *args)
{
    LOG_D("drv_lcd_control cmd is: %d\n", cmd);
    switch (cmd)
    {
        case RTGRAPHIC_CTRL_RECT_UPDATE:
        break;

        case RTGRAPHIC_CTRL_POWERON:
        {
            /* LCD 退出睡眠模式 */
            lcd_display_on();
            lcd_exit_sleep();
        }
        break;

        case RTGRAPHIC_CTRL_POWEROFF:
        {
            /* LCD 進入睡眠模式 */
            lcd_display_off();
            lcd_enter_sleep();
        }
        break;

        case RTGRAPHIC_CTRL_GET_INFO:
        {
            /* 獲取 LCD 引數 */
            memcpy(args, &lcd_info, sizeof(lcd_info));
        }
        break;


        default:
            return -RT_EINVAL;
    }

    return RT_EOK;
}

static void lcd_fb_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
    rt_uint32_t px_size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1);

    /* 設定螢幕重新整理區域 */
    lcd_draw_area_set(area->x1, area->y1, area->x2, area->y2);

    /* 這裡直接透過 SPI 傳送資料,所以需要單獨將資料引腳拉高 */
    rt_pin_write(LCD_DC_PIN, PIN_HIGH);

    /* SPI 傳送時是 uint_8, 而畫素是  uint_16 */
    rt_spi_send(spi_dev_lcd, color_p, px_size * 2);

    lv_disp_flush_ready(disp_drv);
}

/* 驅動函式實現的結構體 */
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops lcd_ops =
{
    drv_lcd_init,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    RT_NULL,
    drv_lcd_control
};
#endif

/**
 * @brief  LCD 裝置註冊
 *
 * @param  None
 * @retval int 註冊結果
 */
int drv_lcd_hw_init(void)
{
    rt_err_t result = RT_EOK;
    rt_uint32_t lcd_buff_size = LCD_HEIGHT * LCD_WIDTH * 2;

    device.user_data = lcd_fb_flush;

    /* 設定 LCD 裝置資訊 */
    lcd_info.height = LCD_HEIGHT;
    lcd_info.width = LCD_WIDTH;
    lcd_info.bits_per_pixel = LCD_BITS_PER_PIXEL;
    lcd_info.pixel_format = RTGRAPHIC_PIXEL_FORMAT_RGB565;     // 影像的格式(RGB:565)

    /* LCD 顯示緩衝區,大小為顯示一幀影像所需空間 */
    lcd_info.smem_len = lcd_buff_size;

#ifdef RT_USING_DEVICE_OPS
    device.ops     = &lcd_ops;
#else
    device.init    = drv_lcd_init;
    device.control = drv_lcd_control;
#endif

    /* 註冊 LCD 裝置 */
    result = rt_device_register(&device, "lcd", RT_DEVICE_FLAG_RDWR);

    return result;
}

INIT_DEVICE_EXPORT(drv_lcd_hw_init);

七、總結

從上面的過程可以看出,移植 LVGL 的過程很簡單,最主要的是 lcd_fb_flush 函式的實現。需要注意的便是 lv_disp_flush_ready(disp_drv) 這個函式一定要新增,後面的介面可能不重新整理,或者重新整理不正常等現象。最後還需要新增一個標頭檔案,如下所示

點選檢視程式碼
#ifndef LV_CONF_H
#define LV_CONF_H

#define LV_COLOR_DEPTH          16


#endif /*LV_CONF_H*/

相關文章