打印
[应用相关]

AT32移植LVGL_V8具体步骤

[复制链接]
960|7
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2022-7-6 12:00 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 tpgf 于 2022-7-6 12:03 编辑

前言:

大部分单片机移植lvgl的步骤都可以参考以下内容,内容从spi驱动,再到液晶驱动,再到lvgl移植,从一个基础工程到移植完成

一、下载LVGL_V8源码

暂时不需要吧

如果使用AT-START-F403开发板时,不需要进行SRAM的扩展,因为403的SRAM默认就为96K。

如果使用AT-START-F413开发板时,强烈建议进行SRAM的扩展,因为413的SRAM默认只有32K,

很容易就会超出范围。

源码地址:LVGL源码https://github.com/lvgl/lvgl

提取出以下文件

1.examples/porting

2.src

3.lvgl.h lv_conf.h

二、SPI驱动

目前只做了spi屏的移植,并口屏后面有机会再补充

1.spi初始化

目前使用AT32提供的固件库,刚开始可以不使用SPI DMA,可以验证spi的配置是否正确,再同过DMA提升刷屏速度

#if (SPI_USE_DMA == 0)

/**

  * @brief  spi configuration.

  * @param  none

  * @retval none

  */

void SPI3_Init(void)

{

        spi_init_type spi_init_struct;

        //gpio_config();

        spi_i2s_reset(SPI3);

        crm_periph_clock_enable(CRM_SPI3_PERIPH_CLOCK, TRUE);

        spi_default_para_init(&spi_init_struct);

        spi_init_struct.transmission_mode = SPI_TRANSMIT_HALF_DUPLEX_TX;

        spi_init_struct.master_slave_mode = SPI_MODE_MASTER;

        spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_8;

        spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;

        spi_init_struct.frame_bit_num = SPI_FRAME_8BIT;

        spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_LOW;

        spi_init_struct.clock_phase = SPI_CLOCK_PHASE_1EDGE;

        spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;

        if(spi_init_struct.cs_mode_selection == SPI_CS_HARDWARE_MODE)

        {

                spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;

        }

        spi_init(SPI3, &spi_init_struct);

        spi_enable(SPI3, TRUE);

}

#else

uint8_t spi3_tx_buffer[BUFFER_SIZE];

void SPI3_Init(void)

{

        dma_init_type dma_init_struct;

        spi_init_type spi_init_struct;

/* SPI_MASTER_Tx_DMA_Channel configuration ---------------------------------------------*/

        /**************DMA 配置 *****************/

        crm_periph_clock_enable(CRM_DMA2_PERIPH_CLOCK, TRUE);

        dma_reset(SPI_MASTER_Tx_DMA_Channel);

        dma_default_para_init(&dma_init_struct);

        dma_init_struct.buffer_size = BUFFER_SIZE;

        dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;

        dma_init_struct.memory_base_addr = (uint32_t)spi3_tx_buffer;

        dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;

        dma_init_struct.memory_inc_enable = TRUE;

        dma_init_struct.peripheral_base_addr = (uint32_t)&SPI3->dt;

        dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;

        dma_init_struct.peripheral_inc_enable = FALSE;

        dma_init_struct.priority = DMA_PRIORITY_MEDIUM;

        dma_init_struct.loop_mode_enable = FALSE;  //是否循环模式

        dma_init(DMA2_CHANNEL2, &dma_init_struct);

        /**************SPI 配置 *****************/

        crm_periph_clock_enable(CRM_SPI3_PERIPH_CLOCK, TRUE);

        spi_default_para_init(&spi_init_struct);

        spi_init_struct.transmission_mode = SPI_TRANSMIT_HALF_DUPLEX_TX;

        spi_init_struct.master_slave_mode = SPI_MODE_MASTER;

        spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_8;

        spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;

        spi_init_struct.frame_bit_num = SPI_FRAME_8BIT;

        spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_HIGH;

        spi_init_struct.clock_phase = SPI_CLOCK_PHASE_2EDGE;

        spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;        

        spi_init(SPI3, &spi_init_struct);

        spi_i2s_dma_transmitter_enable(SPI3, TRUE);

        //中断配置

        /* enable transfer full data intterrupt */

        dma_interrupt_enable(SPI_MASTER_Tx_DMA_Channel, DMA_FDT_INT, TRUE);

        /* dma1 channel1 interrupt nvic init */

        nvic_irq_enable(DMA2_Channel2_IRQn, 1, 0);

        /* config flexible dma for usart2 tx */

        dma_flexible_config(DMA2, FLEX_CHANNEL2, DMA_FLEXIBLE_SPI3_TX);

         spi_enable(SPI3, TRUE);

}

void DMA2_Channel2_IRQHandler(void)

{

    /*DMA发送完成中断*/

    if(dma_flag_get(DMA2_FDT2_FLAG))

   {

      LCD_CS_SET;

      dma_flag_clear(DMA2_FDT2_FLAG);

          lv_disp_flush_complete();/* tell lvgl that flushing is done */

      dma_channel_enable(DMA2_CHANNEL2, FALSE);

            }

}

void disp_spi_dma_send(const void* buf, uint32_t size)

{

        LCD_RS_SET;

    LCD_CS_CLR;

        while(spi_i2s_flag_get(SPI3,SPI_I2S_BF_FLAG) == SET){};

        dma_channel_enable(DMA2_CHANNEL2, FALSE); /* usart2 tx begin dma transmitting */

        DMA2_CHANNEL2->maddr = (uint32_t)buf;

    DMA2_CHANNEL2->dtcnt = size;

    dma_channel_enable(DMA2_CHANNEL2, TRUE);

    /*** 不开DMA完成中断的执行 ***/

//         while(spi_i2s_flag_get(SPI3,SPI_I2S_BF_FLAG) == SET){};

//   while(dma_flag_get(DMA2_FDT2_FLAG) == RESET){};

//   dma_flag_clear(DMA2_FDT2_FLAG);

//         lv_disp_flush_complete();

}


三、lcd驱动1.spi lcd 驱动移植基本上将以下一个函数实现即可
吐槽一下,液晶技术支持一般就提供一个模拟spi的驱动
虽然通用性强,但是太慢了
static void LCD_WR_BYTE(u8 cmd,u8 regval)
{   
         int i;         
        if(cmd)
        {
                LCD_RS_CLR;
        }
        else
        {
                LCD_RS_SET;
        }
     LCD_CS_CLR;
#if USE_SPI
         LCD_WRITE_BYTE(regval);
#else
         for(i=0;i<8;i++)
         {
         if(regval &0x80)
         {
         LCD_SDA_SET;
         }
         else LCD_SDA_CLR;
         LCD_SCL_CLR;
         LCD_SCL_SET;
         regval <<=1;
         }                  
#endif                 
     LCD_CS_SET;        
}

void LCD_WRITE_BYTE(uint8_t DATA)
{
        spi_i2s_data_transmit(LCD_SPI, DATA);
        while(spi_i2s_flag_get(LCD_SPI, SPI_I2S_BF_FLAG) == SET ){};
}


2.lcd初始化中需要注意的点1.一般需要更改的就是下面更改刷屏方向,以及RGB排列 LCD_WR_DATA(0x00|MADCTL_MV|MADCTL_RGB);   


2. 16位颜色数据高低字节顺序

从发送颜色数据的函数可以看出,先发的是16位颜色数据低字节,再发高字节
而 使用SPI DMA发送时是按照

at32 stm32等都是小端模式,即 16位数据 或者32位数据等,是低字节存放在高地址中

使用8位spi传输时,16位像素的低字节被先写入,而高字节被后写入,这就导致了屏幕反色的问题,

解决这个问题,有三种方案,

1 .将SPI写入改为16位模式,这样能够使高字节先写入

2 .写入前先进行字节反转

3 .在LVGL上层进行颜色翻转,lv_conf.h中配置了#define LV_COLOR_16_SWAP 1

这是最简单的方法,后面也会提到。有时候忽略这个就会出现花屏

//发送颜色数据

void Lcd_Write_Data(u16 dat16)

{

  LCD_WR_DATA(dat16>>8);

  LCD_WR_DATA(dat16);        

}         

三、LVGL配置

lv_conf.h中需要注意的几个配置

/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/

#define LV_COLOR_DEPTH 16

/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/

#define LV_COLOR_16_SWAP 1

#define LV_MEM_SIZE (24U * 1024U)          /*[bytes]*/

#define LV_SPRINTF_CUSTOM 1


1 .LV_COLOR_16_SWAP 1 16位颜色数据高低字节反转 使用spi屏的时候一般都要用
2. LV_MEM_SIZE 一些单片机的SRAM可能只有64k,甚至32k的大小,lvgl直接占48K肯定不合适
3. LVGL lv_label_set_text_fmt 显示只有f
解决方式https://blog.csdn.net/weixin_44684950/article/details/124426341


使用特权

评论回复
沙发
tpgf|  楼主 | 2022-7-6 12:01 | 只看该作者
本帖最后由 tpgf 于 2022-7-6 12:05 编辑


四、LVGL与lcd屏幕接口实现

屏幕接口函数都在lv_port_disp.c中

1 lv_port_disp_init

lv_port_disp_init删掉注释,主要内容为

void lv_port_disp_init(void)

{   

    disp_init();

    /**

     * LVGL requires a buffer where it internally draws the widgets.

     * Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.

     * The buffer has to be greater than 1 display row

     *

     * There are 3 buffering configurations:

     * 1. Create ONE buffer:

     *      LVGL will draw the display's content here and writes it to your display

     *

     * 2. Create TWO buffer:

     *      LVGL will draw the display's content to a buffer and writes it your display.

     *      You should use DMA to write the buffer's content to the display.

     *      It will enable LVGL to draw the next part of the screen to the other buffer while

     *      the data is being sent form the first buffer. It makes rendering and flushing parallel.

     *

     * 3. Double buffering

     *      Set 2 screens sized buffers and set disp_drv.full_refresh = 1.

     *      This way LVGL will always provide the whole rendered screen in `flush_cb`

     *      and you only need to change the frame buffer's address.

     */

/********************更改 by WS ************************************************/

    //三种选一种  不开DMA 用第一种

        // 开DMA双缓冲用第二种

        #if (SPI_USE_DMA != 1)

    /* Example for 1) */

    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*/

#else

    /* Example for 2) */

    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*/

#endif

//    /* Example for 3) also set disp_drv.full_refresh = 1 below*/

//    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_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, MY_DISP_VER_RES * LV_VER_RES_MAX);   /*Initialize the display buffer*/

/********************更改 by WS END ************************************************/

    static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/

    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*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 = disp_flush;

/********************更改 by WS ************************************************/

    //三种选一种  不开DMA 用第一种

        // 开DMA双缓冲用第二种

    /*Set a display buffer*/

#if (SPI_USE_DMA != 1)

    disp_drv.draw_buf = &draw_buf_dsc_1;

#else

        disp_drv.draw_buf = &draw_buf_dsc_2;

#endif

    /*Required for Example 3)*/

    //disp_drv.full_refresh = 1

    //disp_drv.gpu_fill_cb = gpu_fill;

    lv_disp_drv_register(&disp_drv);

}

//封装一下方便外部调用

void lv_disp_flush_complete(void)

{

        lv_disp_flush_ready(disp_drv_p);

}


方式1一个数组做缓冲区,节省空间,速度偏慢。
方式2两个数组做缓冲区,空间需要大,速度快点。
方式3也是双缓冲区,但是是每次都刷新整个屏幕。
三种必须选一种,不然内存不够

disp_flush 刷屏函数
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

    int32_t x;
    int32_t y;
        
        int16_t w = (area->x2 - area->x1 + 1);
        int16_t h = (area->y2 - area->y1 + 1);
    uint32_t size = w * h * 2;
        
        disp_drv_p = disp_drv;
#if SPI_USE_DMA
        //设置刷新区域
        Address_set(area->x1, area->y1, area->x2, area->y2);
//        LCD_RS_SET;
//    LCD_CS_CLR;
        
        disp_spi_dma_send(color_p, size);
#else
        
    for(y = area->y1; y <= area->y2; y++) {
        for(x = area->x1; x <= area->x2; x++) {
            /*Put a pixel to the display. For example:*/
            /*put_px(x, y, *color_p)*/
                        
                        LCD_DrawPoint(x,y,color_p->full);
            color_p++;
        }
    }

    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
#endif
}

同样通过宏定义区分是否使用DMA
使用spi直接发送要注意 color_p->full为颜色值
LCD_DrawPoint(x,y,color_p->full);

五、设置心跳

需要调用两个函数

lv_tick_inc(1);                        // 这个函数设置心跳,参数1代表1ms。通常将他放在1毫秒中断一次的定时器中断处理中

lv_task_handler();                // 这个函数用来处理LVGL的工作,每心跳一次,这里面就执行一次。

lv_tick_inc(1);


测试时可以直接放到主函数循环中,后面可以放到滴答定时器或者普通定时器中调用


六、优化显示速度(重要)
1.采用dma方式填充 双缓冲
1.中断调用 lv_disp_flush_ready( disp_drv_p); 不死等DMA发送完成


2.更改缓冲大小,尽量大


3.LVGL帧率限制
首先,LVGL是有一个帧率刷新周期的宏定义,LVGL会通过LVGL内部的tick,定时去刷屏幕,也就是说该宏定义限定了LVGL刷屏帧率的上限,默认满帧33帧。

#define LV_DISP_DEF_REFR_PERIOD 30 /[ms]/

这里我们直接设10ms刷新一次,满帧100帧。

4.LV_USE_PERF_MONITOR 随时监控帧率

2. 修改at32堆栈大小3. 扩展SRAM???4.C++中使用LVGL

使用keil在cpp文件中包含lvgl.h时报错 expected an expression
查看相关代码,已经做过cpp相关兼容了

#if _LV_COLOR_HAS_MODERN_CPP

/*Fix msvc compiler error C4576 inside C++ code*/

#define _LV_COLOR_MAKE_TYPE_HELPER lv_color_t

#else

#define _LV_COLOR_MAKE_TYPE_HELPER (lv_color_t)

#endif




解决方法
参考http://whycan.run/t_830.html
分享解决littlevgl移植到keil 5.24版本出现的编译错误的办法

littlevgl 大量用到 gnu扩展和 C99语法,
如果编译器不支持就要对代码大动干戈,
还好 MDK 5.24a 已经对C99和GNU支持很好了,
只需要轻轻设置一下 C/C++ 编译参数即可.

否则,你将会遇到以下恼人的编译错误:

增加编译flag --gnu





使用特权

评论回复
板凳
muyichuan2012| | 2022-7-6 15:14 | 只看该作者
最近刚好有贴友在找LVGL V8的移植方法,太及时了。

使用特权

评论回复
地板
houjiakai| | 2022-7-9 19:00 | 只看该作者
如何实现多个圆操作?   

使用特权

评论回复
5
abotomson| | 2022-7-9 21:05 | 只看该作者
LVGL_V8不好用   

使用特权

评论回复
评论
muyichuan2012 2022-7-10 12:04 回复TA
V8很好用,而且新增许多功能。 唯一的问题就是许多V7的API在V8s上API接口变了。 
6
touser| | 2022-7-9 21:32 | 只看该作者
v7和v8的api都不一样了。   

使用特权

评论回复
7
janewood| | 2022-7-10 14:09 | 只看该作者
如何缩减LVGL的内存呢?  

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

1949

主题

15676

帖子

12

粉丝