RTT小师弟 · 2022年05月10日

移植 LVGL STM32F407VGT6 8bit FSMC TFT-LCD

本文由RT-Thread论坛@ppacctv 原创发布:https://club.rt-thread.org/ask/article/15d851442a2670c3.html

我的板子是这样的,MCU是STM32F407VGT6

tb_image_share_1652025345850_edit_224568266930835.png

我的TFT-LCD 是这样的,驱动是ili9486,320x480,8bit并口

tb_image_share_1652025279385_edit_224580031517292.png

组合到一起是这样的

IMG_20220508_235640.jpg

LCD 找卖家要的驱动

图片3.png

第一步

第一次移植,不确定问题会在哪发生,所以
Keil + STM32CubeMX
先裸机驱动LCD,验证代码的正确性,减少问题出在LCD这边的可能性。

第二步

裸机没问题,使用RT-Thread Studio新建工程。

图片1.png

在RT-Thread Studio 里,双击 CubeMX Settings,打开CubeMX进行硬件配置

图片2.png

下面是我的配置,Timing的参数先保持CubeMX生成的默认值就好,后面可以慢慢调,调不好LCD没显示的,或者不稳定,不利于移植

图片4.png

时钟、串口、FSMC,设置完,File 菜单,save一下,然后右上角生成 CODE,完事关闭CubeMX。编译一下,应该没有错误。

第三步


https://github.com/RT-Thread/...
下载整个lvgl文件夹,复制到我们工程的applications 文件夹下;

https://github.com/RT-Thread/...
下载drv_lcd.c/drv_lcd.h 复制到我们工程的drivers 文件夹下;
还有我们的LCD驱动,建个ili9486文件夹,放到 drivers 文件夹下。

然后我们右键我们的工程,属性->C/C++常规->路径和符号->包含->GNU C,将我们的lvgl、ili9486、lvgl/demo 文件夹加入。
这步总之就是将以上文件(夹)加入工程并确保能够被找到,怎么加,个人随便!

图片5.png

第四步

编辑 drv_lcd.h

#define LCD_W 320    //LCD水平宽
#define LCD_H 480    //LCD垂直高

#define LCD_ADDR_BASE        0x60000000    //FSMC地址,命令RAM地址
#define LCD_REGSELECT_BIT    16
#define LCD_ADDR_DATA        (LCD_ADDR_BASE + (1 << (LCD_REGSELECT_BIT + 2)) - 2)    //数据RAM地址
#define LCD_ADDR_REG        LCD_ADDR_BASE    //为了方便记忆,改个名

void lcd_fill_array(rt_uint16_t x_start, rt_uint16_t y_start, rt_uint16_t x_end, rt_uint16_t y_end, void *pcolor);

编辑 drv_lcd.c

保留

static struct drv_lcd_device _lcd;

以上,及

void lcd_fill_array(rt_uint16_t x_start, rt_uint16_t y_start, rt_uint16_t x_end,
        rt_uint16_t y_end, void *pcolor) {//……}
static rt_err_t drv_lcd_init(struct rt_device *device){//……}
struct rt_device_graphic_ops fsmc_lcd_ops = {//……}

以下的内容,其它都可以删掉,注意定义的一些结构体,后面少了再加。

static rt_err_t drv_lcd_init(struct rt_device *device) {

    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_GPIOF_CLK_ENABLE();
    __HAL_RCC_GPIOG_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    extern void MX_FSMC_Init(void);//CubeMX生成的,图方便把static去掉了
    MX_FSMC_Init();
    rt_thread_mdelay(50);
    ili9486_Init();//驱动程序提供的初始化函数
    return RT_EOK;
}

void lcd_fill_array(rt_uint16_t x_start, rt_uint16_t y_start, rt_uint16_t x_end, rt_uint16_t y_end, void *pcolor) {

    rt_uint16_t *pixel = RT_NULL;
    pixel = (rt_uint16_t *)pcolor;
    ili9486_DrawRGBImage(x_start, y_start, x_end-x_start+1, y_end-y_start+1, pixel);
}

向给定区域填充内容,内容由 *pcolor 指向,LVGL好像就靠它绘图的。
注意,为社么要 +1 ,因为 3到5,有5-3+1个端点,分别是3,4,5号端点,LCD填充的是点。

struct rt_device_graphic_ops fsmc_lcd_ops = { RT_NULL, RT_NULL,
        RT_NULL,
        RT_NULL,
        RT_NULL};

直接使用RT_NULL填充,在LVGL当前我没发现有用的地方,有用到参照源文件加就好。

static rt_err_t drv_lcd_control(struct rt_device *device, int cmd, void *args) {
    struct drv_lcd_device *lcd = LCD_DEVICE(device);
    switch (cmd) {
        case RTGRAPHIC_CTRL_GET_INFO: {
            struct rt_device_graphic_info *info =
                    (struct rt_device_graphic_info *) args;

            RT_ASSERT(info != RT_NULL);

            //this needs to be replaced by the customer
            info->pixel_format = lcd->lcd_info.pixel_format;
            info->bits_per_pixel = lcd->lcd_info.bits_per_pixel;
            info->width = LCD_W;//自己LCD的水平宽度
            info->height = LCD_H;//自己LCD的垂直高度
        }
            break;
    }

    return RT_EOK;
}

后面没有需要改的,最后的lcd_fill,需要的话可以用来测试,但要实现LCD_Clear函数,使用驱动提供的 ili9486_FillRect 函数实现一下。

void LCD_Clear(uint16_t color) {
    ili9486_FillRect(0, 0, LCD_W, LCD_H, color);
}

编辑lv_conf.h

具体意义不说了,论坛教程很多,就是注意自己LCD的色彩深度、宽高……

#define LV_COLOR_16_SWAP 0
#define LV_COLOR_DEPTH 16
#define LV_USE_PERF_MONITOR 1

#include <rtconfig.h>
#define LV_HOR_RES_MAX          320    //LCD的水平宽
#define LV_VER_RES_MAX          480    //LCD的垂直高
#define LV_USE_DEMO_RTT_MUSIC       1
#define LV_DEMO_RTT_MUSIC_AUTO_PLAY 1
#define LV_FONT_MONTSERRAT_12       1
#define LV_FONT_MONTSERRAT_16       1

编辑lv_port_disp.c

这个文件很关键,(个人看法,大佬可以打个脸)本质就是 LVGL 库提供给显示驱动的接口,要完成移植,就的实现这里面的接口。下面是我的实现。

#define MY_DISP_HOR_RES LCD_W        //我显示器每行LCD_W个点

static lv_disp_draw_buf_t disp_buf_dsc_2;        //可以查看移植模板
static lv_disp_drv_t disp_drv;

//注意缓存大了编译会提示RAM超限的,下面是我更具自己应用的实际取的
//每个缓存一次缓存20行,每行[MY_DISP_HOR_RES个点
lv_color_t buf_2_1[MY_DISP_HOR_RES * 20];        //定义了2块缓存
lv_color_t buf_2_2[MY_DISP_HOR_RES * 20];        //定义了2块缓存

//刷新回调函数
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) {
//主要就是lcd_fill_array函数的实现,在我们的drv_lcd.c 里面实现的
//画图就靠它
    lcd_fill_array(area->x1, area->y1, area->x2, area->y2, color_p);
    lv_disp_flush_ready(disp_drv);
}

void lv_port_disp_init(void) {

        lv_disp_draw_buf_init(&disp_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10);

    lv_disp_drv_init(&disp_drv);

    disp_drv.hor_res = LCD_W;        //LCD的水平宽度
    disp_drv.ver_res = LCD_H;        //LCD的垂直高度
    disp_drv.draw_buf = &disp_buf_dsc_2;
    disp_drv.flush_cb = disp_flush;        //回调函数

    lv_disp_drv_register(&disp_drv);
}

lv_port_indev.h/c个人不涉及,忽略,如编译有问题,请酌情处理!

第五步

双击打开RT-Thread Setting,软件包->多媒体包->LVGL,勾选LVGL及player demo就好。

图片6.png

保存,编译,下载,移植完毕。

不会用编辑器,有点乱!

推荐阅读
关注数
8074
内容数
181
小而美的物联网操作系统,经过14年的累积发展,RT-Thread 已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过4亿台,成为国人自主开发、国内最成熟稳定和装机量最大的开源 RTOS。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息