栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

AT32移植LVGL

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

AT32移植LVGL

目录
  • 前言:
  • 一、下载LVGL_V8源码
    • 1.examples/porting
    • 2.src
    • 3.lvgl.h lv_conf.h
  • 二、SPI驱动
    • 1.spi初始化
  • 三、lcd驱动
    • 1.spi lcd 驱动移植基本上将以下一个函数实现即可
    • 2.lcd初始化中需要注意的点
      • 1.一般需要更改的就是下面更改刷屏方向,以及RGB排列
      • 2. 16位颜色数据高低字节顺序
  • 三、LVGL配置
  • 四、LVGL与lcd屏幕接口实现
    • 1 lv_port_disp_init
    • disp_flush 刷屏函数
  • 五、设置心跳
  • 六、优化显示速度(重要)
    • 1.采用dma方式填充 双缓冲
    • 2. 修改at32堆栈大小
    • 3. 扩展SRAM???

前言:

大部分单片机移植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)

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;
 
 
	
	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);

	
	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);
	//中断配置
	
	dma_interrupt_enable(SPI_MASTER_Tx_DMA_Channel, DMA_FDT_INT, TRUE);

	
	nvic_irq_enable(DMA2_Channel2_IRQn, 1, 0);

	
	dma_flexible_config(DMA2, FLEX_CHANNEL2, DMA_FLEXIBLE_SPI3_TX);

	  
	spi_enable(SPI3, TRUE);
}


void DMA2_Channel2_IRQHandler(void)
{
    

    if(dma_flag_get(DMA2_FDT2_FLAG))
   {
      LCD_CS_SET;
      dma_flag_clear(DMA2_FDT2_FLAG);
	  lv_disp_flush_complete();
	   
      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); 
	DMA2_CHANNEL2->maddr = (uint32_t)buf;
    DMA2_CHANNEL2->dtcnt = size;
    dma_channel_enable(DMA2_CHANNEL2, TRUE); 
    
//	 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中需要注意的几个配置

#define LV_COLOR_DEPTH 16


#define LV_COLOR_16_SWAP 1

#define LV_MEM_SIZE (24U * 1024U)          

1 .LV_COLOR_16_SWAP 1 16位颜色数据高低字节反转 使用spi屏的时候一般都要用
2. LV_MEM_SIZE 一些单片机的SRAM可能只有64k,甚至32k的大小,lvgl直接占48K肯定不合适

四、LVGL与lcd屏幕接口实现

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

1 lv_port_disp_init

lv_port_disp_init删掉注释,主要内容为

void lv_port_disp_init(void)
{
    
    disp_init();
    

    //三种选一种  不开DMA 用第一种 
	// 开DMA双缓冲用第二种
	
#if (SPI_USE_DMA != 1)
    
    static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[MY_DISP_HOR_RES * 10];                          
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   
#else

    
    static lv_disp_draw_buf_t draw_buf_dsc_2;
    static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10];                        
    static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10];                        
    lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10);   
#endif
//    
//    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];            
//    static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES];            
//    lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, MY_DISP_VER_RES * LV_VER_RES_MAX);   


    static lv_disp_drv_t disp_drv;                         
    lv_disp_drv_init(&disp_drv);                    

    

    
    disp_drv.hor_res = MY_DISP_HOR_RES;
    disp_drv.ver_res = MY_DISP_VER_RES;

    
    disp_drv.flush_cb = disp_flush;

    //三种选一种  不开DMA 用第一种 
	// 开DMA双缓冲用第二种
    
#if (SPI_USE_DMA != 1)
    disp_drv.draw_buf = &draw_buf_dsc_1;
#else
	disp_drv.draw_buf = &draw_buf_dsc_2;
#endif

    
    //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)
{
    

    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++) {
            
            
			
			LCD_DrawPoint(x,y,color_p->full);
            color_p++;
        }
    }

    
    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方式填充 双缓冲 2. 修改at32堆栈大小 3. 扩展SRAM???
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/935894.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号