文章目录
- 前言
- 1:准备工作
-
- 1.1 zynq开发板(显示屏部分至少28可用io,触控部分至少4可用io)
- 1.2 50pin标准RGB显示屏
- 1.3.适合自己开发板与显示屏的转接板
- 组合起来差不多这样
- 2:思路讲解
-
- 2.1:ddr缓存一致性问题
- 2.2:axi vdma持续占用问题
- 2.3:axi vdma缓冲区切换延迟
-
- 在本文中采用我采用三缓冲来解决这个问题
- 3:PL硬件设计
-
-
- 3.1:创建工程 创建block design zynq部分控制器 (略)
- 3.2 添加ip zynq核心
- 3.3 添加video控制ip
- 3.4 配置axi video memory access
- 3.5 配置video timing controller
- 3.6 配置axi4-stream to video out
- 3.7 连接主要数据通路
- 3.8 连接复位与时钟
- 3.9 总体连接
- 4.0 完成(以上分步连线若有错误,以最后这个为准)
- 4.1 综合并导出.xsa文件
-
- 4:PS软件设计
-
-
- 4.1 设置工作区
- 4.1 创建platform
- 4.1 创建application
- 4.2 备份cmake 与链接脚本
- 4.3 修改链接脚本
- 4.4 修改CMakeList.txt
- 注意下面代码我是分功能排列的.实际使用中请参考最后模板,顺序很关键
- 4.5 lvgl下载
- 4.6 lvgl.conf配置
- 到这步所有配置工作就完成了,可以正式开始编程了.下面我会讲解几个重点的.完整工程打包放在最后.
- 4.7 统一构建平台freertos中断控制器排雷
- 4.8 vdma驱动
- 4.9初始化lvgl
- 4.9 显示接口
- 4.9 触控接口
- 5.0 文件打包,未展示的代码已经全部压缩供大家参考
-
- 总结
前言
LVGL(轻量级图形库)是一款开源的图形用户界面(GUI)库,该库支持多种硬件平台,并提供丰富的图形界面组件和动画效果,广泛用于嵌入式领域.其中库的移植主要涉及显示接口,触摸接口,与存储接口.与一般arm平台不同的是zynq平台包括PL PS两部分,可定制化程度高.另外vitis在2023年之后主推的是统一构建平台,而不是旧版本的eclipse套件.新版本的平台由cmake构建对于第一次使用这个平台的开发者造成不少困难.本文介绍了从硬件到软件跑起lvgl的全部流程,特别是vitis软件的操作会更加详细介绍.
下面使用vitis25.2工具链与lvgl9.4进行演示
1:准备工作
1.1 zynq开发板(显示屏部分至少28可用io,触控部分至少4可用io)
1.2 50pin标准RGB显示屏
1.3.适合自己开发板与显示屏的转接板
2:思路讲解
首先这种rgb显示屏都是不带显存的,我们需要在fpga的ddr里面开辟一块区域作为显存,然后arm处理器将要显示的数据写入显存,随后pl端使用两个模块AXI Video Direct Memory Access(axi vdma)和AXI4-Stream to Video Out(axi vout)读取这段显存并显示到LCD.这个架构看似很合理但是与生俱来就会带来一些bug,下面我会讲解发生的原理和解决方法.
2.1:ddr缓存一致性问题
lvgl在绘制过程中通常需要一个缓冲区,可以是全屏大小的也可以是小于全屏.lvgl会对这个缓冲区的数据进行图形运算,但是由于Cache的存在数据通常不会在ddr立即刷新,如果直接进行ddr读取就会新旧混杂.解决的方法也很简单,只需一段程序手动刷新数据Cache即可.函数原型如下.
void Xil_DCacheFlushRange(INTPTR adr, u32 len);//编程中尽量使用区域刷新,只刷新用得到的地方
void Xil_DCacheFlush(void);
2.2:axi vdma持续占用问题
假如设置了一个显示屏刷新率为60hz,那么vdma的行为就是一直以60hz持续读取块内存.很显然vdma和lvgl不能同时读写一段内存,有的人会想双缓冲乒乓操作就可以,实际上还有第三个问题约束缓冲区的使用
2.3:axi vdma缓冲区切换延迟
在lvgl显示刷新函数需要命令vdma切换缓冲区,最坏的情况是vdma需要继续完成上一帧的读取才会开始读取新的缓冲区,但此过程中上一帧的内容已经被lvgl修改,很显然双缓冲区不能满足需要.这里我提出两种解决办法,并且引用hello fpga论坛的一种方法: 1:使用lvgl三缓冲功能,三重缓冲相比双缓冲提升了渲染与数据传输的并行性.当一个缓冲区完成渲染而另一个正在进行DMA传输时,第三个缓冲区可立即开始下一帧的渲染.在最坏的情况下旧帧刚读取到第一个像素,需要继续读取完成才能切换新的帧.此时lvgl会有两个缓冲区可以进行新的渲染.大幅提高了系统的可靠性. 2:开启vdma中断功能,将lvgl刷新与dma就绪采用"与"逻辑进行同步.确保每次切换都已经完成上一帧读取. 3:第三个方法引用自hello fpga论坛(基于本站 ZYNQ 主板的 LVGL(V8.3.10版本)的手把手移植教程(VDMA+DMA方式)),这个方法思路如下,建立四个屏幕大小缓冲区,两个用于显存,两个用于lvgl.将这四个缓冲区进行乒乓操作.涉及到的块内存复制则采用arm核心自带的dma.我的评价在lvgl8版本确实是个不错的方法,进入lvgl9版本后,库提供了新的api,新的刷屏逻辑.大块的内存复制不是理智的
在本文中采用我采用三缓冲来解决这个问题
3:PL硬件设计
3.1:创建工程 创建block design zynq部分控制器 (略)
3.2 添加ip zynq核心
开启两个axi接口,一主一从
开启uart iic ttc0 ttc1(可选) emio.注意这里ttc0主要用于freertos.另外ttc1总会用得着,建议全部开启. emio开启两个,用于触控rst和int
开一个时钟用于axi互联,由于arm核心pll只有四个.这里建议大家以后做工程只有axi互联使用arm自带的pll
手动添加复位ip和axi互联(这里直接使用smartconnect),除了axi video部分没有连接,整体系统架构已经完成.
3.3 添加video控制ip
首先拉出三个ip
3.4 配置axi video memory access

3.5 配置video timing controller


3.6 配置axi4-stream to video out

3.7 连接主要数据通路

3.8 连接复位与时钟
连接好主体如下,连接时要注意两个时钟域,1:axi互联时钟.2:rgb显示屏同步时钟.按照这个思路连就不会连接错误了
3.9 总体连接

4.0 完成(以上分步连线若有错误,以最后这个为准)


我修改了端口名和必要的引出,点击验证后自动分配axi总线地址,并且完全正确
4.1 综合并导出.xsa文件
set_property IOSTANDARD LVCMOS33 [get_ports LCD_CLK]
set_property PACKAGE_PIN AA22 [get_ports LCD_CLK]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_BL[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_PLEN[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports LCD_DE]
set_property IOSTANDARD LVCMOS33 [get_ports LCD_H_SYNC]
set_property IOSTANDARD LVCMOS33 [get_ports LCD_V_SYNC]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[23]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[22]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[21]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[20]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[19]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[18]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[17]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[16]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[15]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[14]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[13]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[12]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[11]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[10]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[9]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[8]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {LCD_DATA[0]}]
set_property PACKAGE_PIN AB22 [get_ports LCD_DE]
set_property PACKAGE_PIN AB21 [get_ports LCD_H_SYNC]
set_property PACKAGE_PIN AA21 [get_ports LCD_V_SYNC]
set_property PACKAGE_PIN Y21 [get_ports {LCD_BL[0]}]
set_property PACKAGE_PIN Y20 [get_ports {LCD_PLEN[0]}]
set_property PACKAGE_PIN AB17 [get_ports {LCD_DATA[23]}]
set_property PACKAGE_PIN AA17 [get_ports {LCD_DATA[22]}]
set_property PACKAGE_PIN Y16 [get_ports {LCD_DATA[21]}]
set_property PACKAGE_PIN W16 [get_ports {LCD_DATA[20]}]
set_property PACKAGE_PIN AA14 [get_ports {LCD_DATA[19]}]
set_property PACKAGE_PIN Y14 [get_ports {LCD_DATA[18]}]
set_property PACKAGE_PIN V14 [get_ports {LCD_DATA[17]}]
set_property PACKAGE_PIN V15 [get_ports {LCD_DATA[16]}]
set_property PACKAGE_PIN AB15 [get_ports {LCD_DATA[15]}]
set_property PACKAGE_PIN AB14 [get_ports {LCD_DATA[14]}]
set_property PACKAGE_PIN AA13 [get_ports {LCD_DATA[13]}]
set_property PACKAGE_PIN Y13 [get_ports {LCD_DATA[12]}]
set_property PACKAGE_PIN W13 [get_ports {LCD_DATA[11]}]
set_property PACKAGE_PIN V13 [get_ports {LCD_DATA[10]}]
set_property PACKAGE_PIN W17 [get_ports {LCD_DATA[9]}]
set_property PACKAGE_PIN W18 [get_ports {LCD_DATA[8]}]
set_property PACKAGE_PIN AB20 [get_ports {LCD_DATA[7]}]
set_property PACKAGE_PIN AB19 [get_ports {LCD_DATA[6]}]
set_property PACKAGE_PIN Y19 [get_ports {LCD_DATA[5]}]
set_property PACKAGE_PIN AA19 [get_ports {LCD_DATA[4]}]
set_property PACKAGE_PIN AA16 [get_ports {LCD_DATA[3]}]
set_property PACKAGE_PIN AB16 [get_ports {LCD_DATA[2]}]
set_property PACKAGE_PIN AA18 [get_ports {LCD_DATA[1]}]
set_property PACKAGE_PIN Y18 [get_ports {LCD_DATA[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports UART_0_0_rxd]
set_property IOSTANDARD LVCMOS33 [get_ports UART_0_0_txd]
set_property PACKAGE_PIN M17 [get_ports UART_0_0_rxd]
set_property PACKAGE_PIN L17 [get_ports UART_0_0_txd]
set_property IOSTANDARD LVCMOS33 [get_ports IIC_0_0_scl_io]
set_property IOSTANDARD LVCMOS33 [get_ports IIC_0_0_sda_io]
set_property PACKAGE_PIN U22 [get_ports IIC_0_0_scl_io]
set_property PACKAGE_PIN T22 [get_ports IIC_0_0_sda_io]
set_property IOSTANDARD LVCMOS33 [get_ports clk_in1_0]
set_property PACKAGE_PIN M19 [get_ports clk_in1_0]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_0_tri_io[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {GPIO_0_0_tri_io[0]}]
set_property PACKAGE_PIN V22 [get_ports {GPIO_0_0_tri_io[0]}]
set_property PACKAGE_PIN H19 [get_ports {GPIO_0_0_tri_io[1]}]
最后只需添加引脚约束文件即可,时序约束已经在ip核内部系统自动写好了.正确连好后运行应该不会有报错的.若有错误请再次检查连线.在最后我会把我的转接板pcb文件分享,方便大家直接复制工程
4:PS软件设计
4.1 设置工作区

4.1 创建platform
点击file->new component->platform新建平台
在这里选择工程文件名称和位置
导入vivado生成的xsa文件
选择freertos
创建完成后长这样
修改freertos_tick_rate为1000.这样rtos就会以ms为心跳运行.默认的心跳实在太慢了. 最后点击build构建一次平台
4.1 创建application

选择刚才创建的平台就行了,不需要创建任何源代码,一直点下一步就行了.
在点击build时会弹窗,建议选择手动构建平台,并且保存为工作区偏好.这样每次修改程序就不会重复构建平台浪费时间.但是一旦硬件有变动需要自己手动构建
4.2 备份cmake 与链接脚本

这步非常重要!!!一定要备份!!!
4.3 修改链接脚本

/******************************************************************************
* Copyright (C) 2023 Advanced Micro Devices, Inc. All Rights Reserved.
* SPDX-License-Identifier: MIT
******************************************************************************/
_STACK_SIZE = DEFINED(_STACK_SIZE) ? _STACK_SIZE : 0x80000; /*有变化的部分*/
_HEAP_SIZE = DEFINED(_HEAP_SIZE) ? _HEAP_SIZE : 0x80000; /*有变化的部分*/
_ABORT_STACK_SIZE = DEFINED(_ABORT_STACK_SIZE) ? _ABORT_STACK_SIZE : 1024;
_SUPERVISOR_STACK_SIZE = DEFINED(_SUPERVISOR_STACK_SIZE) ? _SUPERVISOR_STACK_SIZE : 2048;
_IRQ_STACK_SIZE = DEFINED(_IRQ_STACK_SIZE) ? _IRQ_STACK_SIZE : 1024;
_FIQ_STACK_SIZE = DEFINED(_FIQ_STACK_SIZE) ? _FIQ_STACK_SIZE : 1024;
_UNDEF_STACK_SIZE = DEFINED(_UNDEF_STACK_SIZE) ? _UNDEF_STACK_SIZE : 1024;
MEMORY
{
ps7_ddr_0 : ORIGIN = 0x00100000, LENGTH = 0x0C800000 /* 200 MB */
ps7_qspi_linear_0 : ORIGIN = 0xfc000000, LENGTH = 0x01000000
ps7_ram_0 : ORIGIN = 0x00000000, LENGTH = 0x00030000
ps7_ram_1 : ORIGIN = 0xffff0000, LENGTH = 0x0000fe00
ps7_ddr_user : ORIGIN = 0x0C900000, LENGTH = 0x13200000 /*有变化的部分*/
}
/* Specify the default entry point to the program */
ENTRY(_vector_table)
/* Define the sections, and where they are mapped in memory */
SECTIONS
{
.text : {
KEEP (*(.vectors))
*(.boot)
*(.text)
*(.text.*)
*(.gnu.linkonce.t.*)
*(.plt)
*(.gnu_warning)
*(.gcc_execpt_table)
*(.glue_7)
*(.glue_7t)
*(.vfp11_veneer)
*(.ARM.extab)
*(.gnu.linkonce.armextab.*)
*(.note.gnu.build–id)
} > ps7_ddr_0
.init : {
KEEP (*(.init))
} > ps7_ddr_0
.fini : {
KEEP (*(.fini))
} > ps7_ddr_0
.rodata : {
__rodata_start = .;
*(.rodata)
*(.rodata.*)
*(.gnu.linkonce.r.*)
__rodata_end = .;
} > ps7_ddr_0
.rodata1 : {
__rodata1_start = .;
*(.rodata1)
*(.rodata1.*)
__rodata1_end = .;
} > ps7_ddr_0
.sdata2 : {
__sdata2_start = .;
*(.sdata2)
*(.sdata2.*)
*(.gnu.linkonce.s2.*)
__sdata2_end = .;
} > ps7_ddr_0
.sbss2 : {
__sbss2_start = .;
*(.sbss2)
*(.sbss2.*)
*(.gnu.linkonce.sb2.*)
__sbss2_end = .;
} > ps7_ddr_0
.data : {
__data_start = .;
*(.data)
*(.data.*)
*(.gnu.linkonce.d.*)
*(.jcr)
*(.got)
*(.got.plt)
__data_end = .;
} > ps7_ddr_0
.data1 : {
__data1_start = .;
*(.data1)
*(.data1.*)
__data1_end = .;
} > ps7_ddr_0
.got : {
*(.got)
} > ps7_ddr_0
.ctors : {
__CTOR_LIST__ = .;
___CTORS_LIST___ = .;
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE(*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
__CTOR_END__ = .;
___CTORS_END___ = .;
} > ps7_ddr_0
.dtors : {
__DTOR_LIST__ = .;
___DTORS_LIST___ = .;
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE(*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
__DTOR_END__ = .;
___DTORS_END___ = .;
} > ps7_ddr_0
.fixup : {
__fixup_start = .;
*(.fixup)
__fixup_end = .;
} > ps7_ddr_0
.eh_frame : {
*(.eh_frame)
} > ps7_ddr_0
.eh_framehdr : {
__eh_framehdr_start = .;
*(.eh_framehdr)
__eh_framehdr_end = .;
} > ps7_ddr_0
.gcc_except_table : {
*(.gcc_except_table)
} > ps7_ddr_0
.mmu_tbl (ALIGN(16384)) : {
__mmu_tbl_start = .;
*(.mmu_tbl)
__mmu_tbl_end = .;
} > ps7_ddr_0
.ARM.exidx : {
__exidx_start = .;
*(.ARM.exidx*)
*(.gnu.linkonce.armexidix.*.*)
__exidx_end = .;
} > ps7_ddr_0
.preinit_array : {
__preinit_array_start = .;
KEEP (*(SORT(.preinit_array.*)))
KEEP (*(.preinit_array))
__preinit_array_end = .;
} > ps7_ddr_0
.init_array : {
__init_array_start = .;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
__init_array_end = .;
} > ps7_ddr_0
.fini_array : {
__fini_array_start = .;
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array))
__fini_array_end = .;
} > ps7_ddr_0
.drvcfg_sec : {
. = ALIGN(8);
__drvcfgsecdata_start = .;
KEEP (*(.drvcfg_sec))
__drvcfgsecdata_end = .;
__drvcfgsecdata_size = __drvcfgsecdata_end – __drvcfgsecdata_start;
} > ps7_ddr_0
.ARM.attributes : {
__ARM.attributes_start = .;
*(.ARM.attributes)
__ARM.attributes_end = .;
} > ps7_ddr_0
.sdata : {
__sdata_start = .;
*(.sdata)
*(.sdata.*)
*(.gnu.linkonce.s.*)
__sdata_end = .;
} > ps7_ddr_0
.sbss (NOLOAD) : {
__sbss_start = .;
*(.sbss)
*(.sbss.*)
*(.gnu.linkonce.sb.*)
__sbss_end = .;
} > ps7_ddr_0
.tdata : {
__tdata_start = .;
*(.tdata)
*(.tdata.*)
*(.gnu.linkonce.td.*)
__tdata_end = .;
} > ps7_ddr_0
.tbss : {
__tbss_start = .;
*(.tbss)
*(.tbss.*)
*(.gnu.linkonce.tb.*)
__tbss_end = .;
} > ps7_ddr_0
.bss (NOLOAD) : {
__bss_start = .;
*(.bss)
*(.bss.*)
*(.gnu.linkonce.b.*)
*(COMMON)
__bss_end = .;
} > ps7_ddr_0
_SDA_BASE_ = __sdata_start + ((__sbss_end – __sdata_start) / 2 );
_SDA2_BASE_ = __sdata2_start + ((__sbss2_end – __sdata2_start) / 2 );
/* Generate Stack and Heap definitions */
.heap (NOLOAD) : {
. = ALIGN(16);
_heap = .;
HeapBase = .;
_heap_start = .;
. += _HEAP_SIZE;
_heap_end = .;
HeapLimit = .;
} > ps7_ddr_0
.stack (NOLOAD) : {
. = ALIGN(16);
_stack_end = .;
. += _STACK_SIZE;
. = ALIGN(16);
_stack = .;
__stack = _stack;
. = ALIGN(16);
_irq_stack_end = .;
. += _IRQ_STACK_SIZE;
. = ALIGN(16);
__irq_stack = .;
_supervisor_stack_end = .;
. += _SUPERVISOR_STACK_SIZE;
. = ALIGN(16);
__supervisor_stack = .;
_abort_stack_end = .;
. += _ABORT_STACK_SIZE;
. = ALIGN(16);
__abort_stack = .;
_fiq_stack_end = .;
. += _FIQ_STACK_SIZE;
. = ALIGN(16);
__fiq_stack = .;
_undef_stack_end = .;
. += _UNDEF_STACK_SIZE;
. = ALIGN(16);
__undef_stack = .;
} > ps7_ddr_0
.lv_buf (NOLOAD) : { /*有变化的部分*/
. = ALIGN(64);
__lv_buf_start = .;
KEEP(*(.lv_buf))
KEEP(*(.lv_buf.*))
__lv_buf_end = .;
. = ALIGN(64);
} > ps7_ddr_user
end = .;
}
与默认脚本有变化的部分我已经打上注释,首先将ddr芯片分为了两个区域ps7_ddr_0与ps7_ddr_user.随后将堆栈大小加大.从下面可以看到堆栈占用的空间是ps7_ddr_0,只要够lvgl用就可以了,不用一味追求大. 其中最重要的部分如下:新建了一个空间为lv_buf他在物理内存中是连续并且64位对齐的,这对dma读取很有用处
.lv_buf (NOLOAD) : { /*有变化的部分*/
. = ALIGN(64);
__lv_buf_start = .;
KEEP(*(.lv_buf))
KEEP(*(.lv_buf.*))
__lv_buf_end = .;
. = ALIGN(64);
} > ps7_ddr_user
例如创建缓冲区我可以这样写,这样程序运行后三个数组就会在确定的地址ORIGIN = 0x0C900000, LENGTH = 0x13200000中连续分布,做到自主可控游刃有余
static uint8_t frame_buffer0[SCREEN_WIDTH*SCREEN_HEIGHT*3] __attribute__ ((section(".lv_buf"), aligned(64)));
static uint8_t frame_buffer1[SCREEN_WIDTH*SCREEN_HEIGHT*3] __attribute__ ((section(".lv_buf"), aligned(64)));
static uint8_t frame_buffer2[SCREEN_WIDTH*SCREEN_HEIGHT*3] __attribute__ ((section(".lv_buf"), aligned(64)));
4.4 修改CMakeList.txt

注意下面代码我是分功能排列的.实际使用中请参考最后模板,顺序很关键
添加如下代码以支持c++与c混合编程
enable_language(C CXX ASM)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
list(APPEND _deps ${USER_LINK_LIBRARIES} )
list(APPEND _deps c gcc)
collector_list (_deps PROJECT_LIB_DEPS)
set(CMAKE_C_COMPILER ${CMAKE_CXX_COMPILER})
添加以下lvgl构建宏
set(LV_CONF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lvgl/lv_conf.h)
set(LV_CONF_BUILD_DISABLE_EXAMPLES ON)
set(LV_CONF_BUILD_DISABLE_DEMOS ON)
add_subdirectory(lvgl)
上面的代码顺序会有先后调整,最终版本如下
# Copyright (C) 2023 – 2025 Advanced Micro Devices, Inc. All rights reserved.
# SPDX–License–Identifier: MIT
cmake_minimum_required(VERSION 3.16)
include(${CMAKE_SOURCE_DIR}/Empty_applicationExample.cmake)
# Include any additional CMake files here
include(${CMAKE_CURRENT_SOURCE_DIR}/UserConfig.cmake)
set(APP_NAME RGB_SCREEN_VSW)
project(${APP_NAME})
find_package(common)
enable_language(C CXX ASM)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
collect(PROJECT_LIB_DEPS xilstandalone;xiltimer;freertos)
collect(PROJECT_LIB_DEPS xil)
collect(PROJECT_LIB_DEPS gcc)
collect(PROJECT_LIB_DEPS c)
list(APPEND _deps ${USER_LINK_LIBRARIES} )
list(APPEND _deps c gcc)
set(LV_CONF_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lvgl/lv_conf.h)
set(LV_CONF_BUILD_DISABLE_EXAMPLES ON)
set(LV_CONF_BUILD_DISABLE_DEMOS ON)
link_directories(${CMAKE_LIBRARY_PATH})# Use CMAKE_LIBRARY_PATH in link_directories
collector_create (PROJECT_LIB_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}")
collector_create (PROJECT_LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}")
# Add subdirectory dependency if available
collector_list (_deps PROJECT_LIB_DEPS)
collector_list (_headers PROJECT_LIB_HEADERS)
collector_list (_sources PROJECT_LIB_SOURCES)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} current_dir_sources)
list(APPEND _sources ${current_dir_sources})
list (APPEND _sources ${USER_COMPILE_SOURCES})
foreach (source ${_sources})
get_filename_component(ext ${source} EXT)
list(APPEND src_ext ${ext})
endforeach()
if (NOT DEFINED PROJECT_TYPE)
find_project_type ("${src_ext}" PROJECT_TYPE)
endif()
if("${PROJECT_TYPE}" STREQUAL "c++")
collect(PROJECT_LIB_DEPS stdc++)
endif()
collector_list (_deps PROJECT_LIB_DEPS)
list (APPEND _deps ${USER_LINK_LIBRARIES})
string (REPLACE ";" ",-l" _deps "${_deps}")
if (CMAKE_EXPORT_COMPILE_COMMANDS AND
(NOT ${YOCTO}))
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES})
endif()
linker_gen("${CMAKE_SOURCE_DIR}/linker_files/")
string(APPEND CMAKE_C_FLAGS ${USER_COMPILE_OPTIONS})
string(APPEND CMAKE_CXX_FLAGS ${USER_COMPILE_OPTIONS})
string(APPEND CMAKE_C_LINK_FLAGS ${USER_LINK_OPTIONS})
string(APPEND CMAKE_CXX_LINK_FLAGS ${USER_LINK_OPTIONS})
add_subdirectory(lvgl)
if(NOT "${_sources}" STREQUAL "")
add_dependency_on_bsp(_sources)
add_executable(${APP_NAME}.elf ${_sources})
set_target_properties(${APP_NAME}.elf PROPERTIES LINK_DEPENDS ${USER_LINKER_SCRIPT})
set(_linker_command "-Wl,-T -Wl,\\"${USER_LINKER_SCRIPT}\\" -L\\"${CMAKE_SOURCE_DIR}/\\" -L\\"${CMAKE_LIBRARY_PATH}/\\" -L\\"${USER_LINK_DIRECTORIES}/\\" -Wl,–start-group,-l${_deps} -Wl,–end-group")
target_link_libraries(${APP_NAME}.elf ${_linker_command})
target_link_libraries(${APP_NAME}.elf lvgl)
target_compile_definitions(${APP_NAME}.elf PUBLIC ${USER_COMPILE_DEFINITIONS})
target_include_directories(${APP_NAME}.elf PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(${APP_NAME}.elf PUBLIC ${USER_INCLUDE_DIRECTORIES})
print_build_info_target(${APP_NAME}.elf "${CMAKE_C_FLAGS}" "${USER_COMPILE_DEFINITIONS}" "${CMAKE_C_LINK_FLAGS}" "${_linker_command}")
print_elf_size(CMAKE_SIZE ${APP_NAME})
endif()
最后修改.cmake第93行set(USER_COMPILE_OTHER_FLAGS “-mfpu=neon”),这样就可以开启fpu的neon函数,以便在lvgl中优化块像素移动

4.5 lvgl下载
完成上面的操作后,就可以开始移植lvgl了,不过在移植前建议大家测试vdma功能与显示屏色彩.特别是每个颜色通道高低位是否正确,这里我就踩过坑.
首先使用git命令将lvgl源码拷贝过来
4.6 lvgl.conf配置
将配置模板修改为lv_conf.h复制到上级文件夹 再将第一行改为"1"
#if 1 /* Set this to "1" to enable content */
#ifndef LV_CONF_H
#define LV_CONF_H
/* If you need to include anything here, do it inside the `__ASSEMBLY__` guard */
#if 0 && defined(__ASSEMBLY__)
#include "my_include.h"
#endif
修改颜色格式
#define LV_COLOR_DEPTH 24
修改printf支持,若标签浮点数显示不正常,这里修改为CLIB就行了
#define LV_USE_STDLIB_SPRINTF LV_STDLIB_CLIB
下面修改lvgl缓存大小
#define LV_MEM_SIZE (128 * 1024U)
#define LV_DRAW_LAYER_SIMPLE_BUF_SIZE (32 * 1024)
#define LV_DRAW_THREAD_STACK_SIZE (32 * 1024)
修改刷新率与os使用
#define LV_DEF_REFR_PERIOD 17
#define LV_DPI_DEF 170
#define LV_USE_OS LV_OS_FREERTOS
这步启用用于优化区域像素搬运能力,与上面修改cmake宏mfpu=neon有直接依赖关系,开启这个选项在压力测试moving wallpaper成绩由原来的29fps提高到45fps
#define LV_USE_DRAW_SW_ASM LV_DRAW_SW_ASM_NEON
开启监视器,若不修改为lv_timer_get_idle在cpu占用率会一直显示100%
#define LV_USE_SYSMON 1
#define LV_SYSMON_GET_IDLE lv_timer_get_idle
#define LV_USE_MEM_MONITOR 1
#define LV_USE_PERF_MONITOR 1
最后可选项编译demo,选自己需要的就行了.想用哪个就把0改为1
#if LV_BUILD_DEMOS
/** Show some widgets. This might be required to increase `LV_MEM_SIZE`. */
#define LV_USE_DEMO_WIDGETS 0
/** Demonstrate usage of encoder and keyboard. */
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
/** Benchmark your system */
#define LV_USE_DEMO_BENCHMARK 1
#if LV_USE_DEMO_BENCHMARK
/** Use fonts where bitmaps are aligned 16 byte and has Nx16 byte stride */
#define LV_DEMO_BENCHMARK_ALIGNED_FONTS 1
#endif
/** Render test for each primitive.
* – Requires at least 480×272 display. */
#define LV_USE_DEMO_RENDER 0
/** Stress test for LVGL */
#define LV_USE_DEMO_STRESS 0
/** Music player demo */
#define LV_USE_DEMO_MUSIC 0
#if LV_USE_DEMO_MUSIC
#define LV_DEMO_MUSIC_SQUARE 0
#define LV_DEMO_MUSIC_LANDSCAPE 0
#define LV_DEMO_MUSIC_ROUND 0
#define LV_DEMO_MUSIC_LARGE 0
#define LV_DEMO_MUSIC_AUTO_PLAY 0
#endif
/** Vector graphic demo */
#define LV_USE_DEMO_VECTOR_GRAPHIC 0
/** GLTF demo */
#define LV_USE_DEMO_GLTF 0
/*—————————
* Demos from lvgl/lv_demos
—————————*/
/** Flex layout demo */
#define LV_USE_DEMO_FLEX_LAYOUT 0
/** Smart-phone like multi-language demo */
#define LV_USE_DEMO_MULTILANG 0
/** Widget transformation demo */
#define LV_USE_DEMO_TRANSFORM 0
/** Demonstrate scroll settings */
#define LV_USE_DEMO_SCROLL 0
/*E-bike demo with Lottie animations (if LV_USE_LOTTIE is enabled)*/
#define LV_USE_DEMO_EBIKE 0
#if LV_USE_DEMO_EBIKE
#define LV_DEMO_EBIKE_PORTRAIT 0 /*0: for 480×270..480×320, 1: for 480×800..720×1280*/
#endif
/** High-resolution demo */
#define LV_USE_DEMO_HIGH_RES 0
/* Smart watch demo */
#define LV_USE_DEMO_SMARTWATCH 0
#endif /* LV_BUILD_DEMOS */
到这步所有配置工作就完成了,可以正式开始编程了.下面我会讲解几个重点的.完整工程打包放在最后.
4.7 统一构建平台freertos中断控制器排雷
在freertos系统下scugic中断控制器会在vTaskStartScheduler()函数被配置配置,在早期的eclipse套件freertos如果用户需要修改scugic配置必须使用extern XScuGic xInterruptController延续先前的配置,并添加自己的中断.否则freertos就会卡死.而在新版本vitis已经不允许这么用了.这里我提供以下解决办法,我也在amd论坛首次回答这个问题.核心思路是将原始寄存器值读取,然后构建新的配置再写入,就可以避免freertos卡死,并且添加新的用户中断.
void setup_fpga_irq(uint32_t intr_id, irq_trigger_t trig, uint8_t priority,Xil_InterruptHandler handler, void *callback_ref){
const XScuGic_Config *cfg = XScuGic_LookupConfig(XPAR_XSCUGIC_0_BASEADDR);
if (cfg == NULL) return;
const UINTPTR dist = cfg->DistBaseAddress;
const UINTPTR cpu = cfg->CpuBaseAddress;
// Configure edge or level in ICFGR: bit[2n+1]=1 => edge, 0 => level.
const UINTPTR icfgr_addr = dist + GIC_DIST_ICFGR_OFFSET(intr_id);
const u32 icfgr_shift = (intr_id & 0x0FU) << 1U;
u32 icfgr = Xil_In32(icfgr_addr);
icfgr &= ~(0x3U << icfgr_shift);
icfgr |= ((trig == IRQ_EDGE ? 0x2U : 0x0U) << icfgr_shift);
Xil_Out32(icfgr_addr, icfgr);
// Set priority and target CPU0 only for this interrupt.
const UINTPTR prio_addr = dist + GIC_DIST_IPRIORITY_OFFSET(intr_id);
const UINTPTR target_addr = dist + GIC_DIST_ITARGETS_OFFSET(intr_id);
const u32 prio_shift = (intr_id & 0x03U) << 3U;
u32 prio_reg = Xil_In32(prio_addr);
prio_reg = (prio_reg & ~(0xFFU << prio_shift)) | (((u32)priority & 0xFFU) << prio_shift);
Xil_Out32(prio_addr, prio_reg);
u32 target = Xil_In32(target_addr);
target = (target & ~(0xFFU << prio_shift)) | (0x01U << prio_shift); // CPU0
Xil_Out32(target_addr, target);
// Register handler and enable interrupt. Do NOT touch PMR/CPU_CTRL.
XScuGic_RegisterHandler(cpu, intr_id, handler, callback_ref);
XScuGic_EnableIntr(dist, intr_id);
}
函数使用方法如下,XAxiVdma_ReadIntrHandler,XGpioPs_IntrHandler是相应外设中断请求二次分发的系统函数,已经在bsp中有定义.这个函数只管理scugic控制器,剩下外设的中断配置正常配置即可.
setup_fpga_irq(XPS_FPGA0_INT_ID,IRQ_EDGE,0xA1, (Xil_InterruptHandler)XAxiVdma_ReadIntrHandler,(void *)&pl_vdma_0);
setup_fpga_irq(XPS_GPIO_INT_ID, IRQ_LEVEL,0xA0, (Xil_InterruptHandler)XGpioPs_IntrHandler,(void *)&ps_emio);
4.8 vdma驱动
在代码前部分定义了几个回调函数,不过后来不需要中断后把这个功能关了,大家可不必理会. 我将下面代码全部用AI加上注释,不得不说AI确实很强大.AI这种东西大胆用就是了
#include "vdma.hpp"
// 全局VDMA实例
XAxiVdma pl_vdma_0;
// 设置回调函数的辅助函数
static inline void vdma_set_callback(XAxiVdma *inst, u32 handler_type,
void (*cb)(void *, u32), void *ref, u32 dir)
{
// 将回调函数指针转换为void*类型
void *cb_ptr = reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(cb));
XAxiVdma_SetCallBack(inst, handler_type, cb_ptr, ref, dir);
}
// 读取完成回调函数
void ReadCallBack(void *CallbackRef, uint32_t Mask)
{
// 检查是否完成标志置位
if (Mask & XAXIVDMA_IXR_COMPLETION_MASK) {
xil_printf("read done\\n"); // 打印完成信息
}
}
// 读取错误回调函数
void ReadErrorCallBack(void *CallbackRef, uint32_t Mask)
{
// 检查是否错误标志置位
if (Mask & XAXIVDMA_IXR_ERROR_MASK) {
xil_printf("read error\\n"); // 打印错误信息
}
}
// 初始化VDMA
void init_pl_vdma0(uint8_t *buf0, uint8_t *buf1, uint8_t *buf2) {
XAxiVdma_Config *pl_vdma_0_config; // VDMA配置结构体
XAxiVdma_DmaSetup read_cfg; // 读取配置结构体
// 查找并初始化VDMA配置
pl_vdma_0_config = XAxiVdma_LookupConfig(VDMA_DEVICE_ID);
if (pl_vdma_0_config == nullptr) return;
XAxiVdma_CfgInitialize(&pl_vdma_0, pl_vdma_0_config, pl_vdma_0_config->BaseAddress);
XAxiVdma_Selftest(&pl_vdma_0); // 执行自检
// 配置读取参数
memset(&read_cfg, 0, sizeof(read_cfg));
read_cfg.VertSizeInput = SCREEN_HEIGHT; // 垂直尺寸
read_cfg.HoriSizeInput = SCREEN_WIDTH * 3; // 水平尺寸(RGB三通道)
read_cfg.Stride = SCREEN_WIDTH * 3; // 行跨度
read_cfg.FrameDelay = 0; // 帧延迟
read_cfg.EnableCircularBuf = 1; // 启用环形缓冲区
read_cfg.EnableSync = 1; // 启用同步
read_cfg.PointNum = 0; // 点数
read_cfg.EnableFrameCounter = 0; // 禁用帧计数器
read_cfg.FixedFrameStoreAddr = 0; // 固定帧存储地址
read_cfg.FrameStoreStartAddr[0] = (UINTPTR)buf0; // 帧缓冲区0地址
read_cfg.FrameStoreStartAddr[1] = (UINTPTR)buf1; // 帧缓冲区1地址
read_cfg.FrameStoreStartAddr[2] = (UINTPTR)buf2; // 帧缓冲区2地址
// 配置VDMA读取通道
XAxiVdma_DmaConfig(&pl_vdma_0, XAXIVDMA_READ, &read_cfg);
// 启用中断(当前注释掉)
// XAxiVdma_IntrClear(&pl_vdma_0, XAXIVDMA_IXR_ALL_MASK, XAXIVDMA_READ);
// vdma_set_callback(&pl_vdma_0, XAXIVDMA_HANDLER_GENERAL, ReadCallBack, (void *)&pl_vdma_0, XAXIVDMA_READ);
// vdma_set_callback(&pl_vdma_0, XAXIVDMA_HANDLER_ERROR, ReadErrorCallBack, (void *)&pl_vdma_0, XAXIVDMA_READ);
XAxiVdma_IntrEnable(&pl_vdma_0, XAXIVDMA_IXR_ALL_MASK, XAXIVDMA_READ);
// 设置帧存储数量并启动DMA
XAxiVdma_SetFrmStore(&pl_vdma_0, 3, XAXIVDMA_READ);
XAxiVdma_DmaSetBufferAddr(&pl_vdma_0, XAXIVDMA_READ, read_cfg.FrameStoreStartAddr);
XAxiVdma_DmaStart(&pl_vdma_0, XAXIVDMA_READ);
XAxiVdma_StartParking(&pl_vdma_0, 0, XAXIVDMA_READ); // 初始停在帧0
}
// 切换帧缓冲区
void vdma_switch_frame(uint32_t index) {
if (index > 2) return; // 检查索引有效性
XAxiVdma_StartParking(&pl_vdma_0, index, XAXIVDMA_READ); // 切换到指定帧
}
#ifndef _VDMA_HPP_
#define _VDMA_HPP_
// 包含必要的头文件
#include "xparameters.h" // Xilinx参数定义
#include "xaxivdma.h" // AXI VDMA驱动
#include "xaxivdma_i.h" // AXI VDMA内部定义
#include "xil_types.h" // Xilinx数据类型
#include <cstdint> // 标准整数类型
#include <string.h> // 字符串操作
// 定义屏幕尺寸常量
#define SCREEN_WIDTH 1024 // 屏幕宽度(像素)
#define SCREEN_HEIGHT 600 // 屏幕高度(像素)
// 定义VDMA设备ID
#ifndef SDT
#define VDMA_DEVICE_ID XPAR_AXIVDMA_0_DEVICE_ID // 传统设备ID定义
#else
#define VDMA_DEVICE_ID XPAR_AXI_VDMA_0_BASEADDR // 基于SDT的设备地址定义
#endif
// 函数声明
void init_pl_vdma0(uint8_t *buf0, uint8_t *buf1, uint8_t *buf2); // 初始化VDMA
void vdma_switch_frame(uint32_t index); // 切换帧缓冲区
#endif
有了这个函数void init_pl_vdma0(uint8_t *buf0, uint8_t *buf1, uint8_t *buf2)无论是否使用lvgl,只要向缓冲区输入颜色数据就可以显示了.
4.9初始化lvgl
与旧版本不同lvgl9.4实测在os的情况下不需要1ms的定时器,只需要lv_tick_set_cb(xTaskGetTickCount)函数即可.区别最大的是三缓冲区的定义,官方手册写的并不详细,下面的代码详细讲解了如何配置
static lv_draw_buf_t third_vid_buf; // 声明静态的LVGL绘图缓冲区结构体,用于存储第三缓冲区的配置信息
lv_color_format_t cf = lv_display_get_color_format(display1); // 获取显示设备display1的像素颜色格式(如RGB565、ARGB8888等)
uint32_t w = lv_display_get_original_horizontal_resolution(display1); // 获取显示设备的原始水平分辨率(宽度)
uint32_t h = lv_display_get_original_vertical_resolution(display1); // 获取显示设备的原始垂直分辨率(高度)
uint32_t stride = lv_draw_buf_width_to_stride(w, cf); // 计算缓冲区行步长(stride),考虑像素格式对齐要求
lv_draw_buf_init(&third_vid_buf, w, h, cf, stride, frame_buffer2, sizeof(frame_buffer0));
// 参数说明:
// – &third_vid_buf: 目标缓冲区结构体指针
// – w: 缓冲区宽度(像素)
// – h: 缓冲区高度(像素)
// – cf: 颜色格式
// – stride: 每行字节步长
// – frame_buffer2: 外部内存指针,用于存储实际像素数据
// – sizeof(frame_buffer0): 外部内存区域大小(字节)
lv_display_set_3rd_draw_buffer(display1, &third_vid_buf);
// 将初始化好的third_vid_buf设置为display1的第三绘图缓冲区
// 该缓冲区可用于三缓冲渲染策略,减少画面撕裂
void lv_task(void *pvParameters)
{
init_touch(TOUCH_REVOLVE_0);
interrupt_init();
init_pl_vdma0(frame_buffer0, frame_buffer1,frame_buffer2);
lv_init();
lv_tick_set_cb(xTaskGetTickCount);
display1 = lv_display_create(SCREEN_WIDTH,SCREEN_HEIGHT);
lv_display_set_buffers(display1, frame_buffer0,frame_buffer1, sizeof(frame_buffer0), LV_DISPLAY_RENDER_MODE_DIRECT);
static lv_draw_buf_t third_vid_buf;
lv_color_format_t cf = lv_display_get_color_format(display1);
uint32_t w = lv_display_get_original_horizontal_resolution(display1);
uint32_t h = lv_display_get_original_vertical_resolution(display1);
uint32_t stride = lv_draw_buf_width_to_stride(w, cf);
lv_draw_buf_init(&third_vid_buf, w, h, cf, stride, frame_buffer2, sizeof(frame_buffer0));
lv_display_set_3rd_draw_buffer( display1, &third_vid_buf );
lv_display_set_flush_cb(display1, my_flush_cb);
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); /*See below.*/
lv_indev_set_read_cb(indev, my_input_read);
//lv_demo_music();
lv_demo_benchmark();
//lv_demo_widgets();
for(;;) {
uint32_t time_till_next = lv_timer_handler();
if(time_till_next == LV_NO_TIMER_READY) time_till_next = LV_DEF_REFR_PERIOD;
vTaskDelay(pdMS_TO_TICKS(time_till_next));
}
}
4.9 显示接口
要注意的是我采用的策略是LV_DISPLAY_RENDER_MODE_DIRECT–全屏缓冲的区域刷新–uint8_t * px_map传回来的是整个屏幕左上角第一个元素的地址而不是要刷新矩形左上角第一个元素的地址.刷新一帧可能会多次调用显示接口,我们不需要每次都修改vdma的缓冲区只需要在最后一次刷屏完成缓存一致性和vdma缓冲区切换即可
void my_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map)
{
if(lv_display_flush_is_last(display)) {
uint32_t index;
if (px_map == frame_buffer0) {
index = 0;
} else if (px_map == frame_buffer1) {
index = 1;
} else if (px_map == frame_buffer2) {
index = 2;
} else {
lv_display_flush_ready(display);
return;
}
Xil_DCacheFlushRange((UINTPTR)px_map, sizeof(frame_buffer0));
vdma_cur_frame = index;
vdma_switch_frame(index);
lv_display_flush_ready(display);
}else{
lv_display_flush_ready(display);
}
4.9 触控接口
下面我提供了支持5点触控的gt911驱动代码,需配合iic外设完成.
#include "gt911.hpp"
extern XIicPs ps_iic0;
extern XGpioPs ps_emio;
touch_point touch_point_t={0,0,0,0,0,0,0,0,0,0,0};
static uint16_t touch_panel_wide=TOUCH_WIDTH;
static uint16_t touch_panel_height=TOUCH_HEIGHT;
static touch_revolve_degree_t touch_revolve_degree=TOUCH_REVOLVE_0;
volatile uint32_t last_time;
void Emio1_Callback(void *Ref, uint32_t Bank, uint32_t Status){
volatile uint32_t current_time =xTaskGetTickCount();
if(current_time – last_time > TOUCH_DEBOUNCE_MS) {
read_touch();
last_time = current_time;
}else{
}
XGpioPs_IntrClearPin(&ps_emio, INT_PIN);
}
static void touch_apply_rotation(uint16_t *x, uint16_t *y) {
uint16_t new_x = *x;
uint16_t new_y = *y;
switch (touch_revolve_degree) {
case TOUCH_REVOLVE_0:
break;
case TOUCH_REVOLVE_90:
new_x = (uint16_t)(touch_panel_wide – 1 – *y);
new_y = *x;
break;
case TOUCH_REVOLVE_180:
new_x = (uint16_t)(touch_panel_wide – 1 – *x);
new_y = (uint16_t)(touch_panel_height – 1 – *y);
break;
case TOUCH_REVOLVE_270:
new_x = *y;
new_y = (uint16_t)(touch_panel_height – 1 – *x);
break;
default:
break;
}
*x = new_x;
*y = new_y;
}
static void gt911_read_reg(uint16_t addr, uint8_t *buf, int len) {
uint8_t send_buf[2];
send_buf[0] = (uint8_t)(addr >> 8);
send_buf[1] = (uint8_t)(addr & 0xFF);
XIicPs_MasterSendPolled(&ps_iic0, send_buf, 2, GT911_ADDRESS);
while (XIicPs_BusIsBusy(&ps_iic0));
XIicPs_MasterRecvPolled(&ps_iic0, buf, len, GT911_ADDRESS);
while (XIicPs_BusIsBusy(&ps_iic0));
}
static void gt911_write_reg(uint16_t addr, uint8_t *buf, int len) {
uint8_t send_buf[10] = {0};
send_buf[0] = (uint8_t)(addr >> 8);
send_buf[1] = (uint8_t)(addr & 0xFF);
memcpy(&send_buf[2], buf, len);
XIicPs_MasterSendPolled(&ps_iic0, send_buf, len + 2, GT911_ADDRESS);
while (XIicPs_BusIsBusy(&ps_iic0));
}
void update_touch_panel_size(touch_revolve_degree_t degree){
touch_revolve_degree=degree;
if (degree == TOUCH_REVOLVE_90 || degree == TOUCH_REVOLVE_270) {
touch_panel_wide = TOUCH_HEIGHT;
touch_panel_height = TOUCH_WIDTH;
} else {
touch_panel_wide = TOUCH_WIDTH;
touch_panel_height = TOUCH_HEIGHT;
}
}
void init_touch(touch_revolve_degree_t degree)
{
update_touch_panel_size(degree);
XGpioPs_WritePin(&ps_emio, RESET_PIN, 0);
XGpioPs_WritePin(&ps_emio, INT_PIN, 0);
vTaskDelay(1);
XGpioPs_WritePin(&ps_emio, RESET_PIN, 1);
vTaskDelay(55);
XGpioPs_SetDirectionPin(&ps_emio, INT_PIN, 0);//input_floating
XGpioPs_SetOutputEnablePin(&ps_emio, INT_PIN, 0);
//XGpioPs_SetIntrTypePin(&ps_emio, INT_PIN , XGPIOPS_IRQ_TYPE_EDGE_RISING);
//XGpioPs_IntrEnablePin(&ps_emio, INT_PIN );
//XGpioPs_SetCallbackHandler(&ps_emio, (void *)&ps_emio, Emio1_Callback);// 始终将回调引用设置为 GPIO 实例,方便在回调中清除中断
uint8_t buffer[5] = {0};
gt911_read_reg(0x8140, buffer, 4);
buffer[3]='\\0';
xil_printf("CTP ID:%s\\n", buffer);
}
void read_touch() {
uint8_t status = 0;
uint8_t buf[40] = {0};
uint8_t touch_num = 0;
touch_point_t.x1=0; touch_point_t.y1=0;
touch_point_t.x2=0; touch_point_t.y2=0;
touch_point_t.x3=0; touch_point_t.y3=0;
touch_point_t.x4=0; touch_point_t.y4=0;
touch_point_t.x5=0; touch_point_t.y5=0;
// Read touch status
gt911_read_reg(0x814E, &status, 1);
// Check if a touch event is detected
if ((status & 0x80) && (status & 0x0F)) {
touch_num = (uint8_t)(status & 0x0F);
if (touch_num == 0) return;
if (touch_num > 5) {
touch_num = 5;
}
// Read all points (8 bytes each)
gt911_read_reg(0x8150, buf, (int)(touch_num * 8));
for (uint8_t i = 0; i < touch_num; ++i) {
uint16_t x = (uint16_t)((buf[i * 8 + 1] << 8) | buf[i * 8 + 0]);
uint16_t y = (uint16_t)((buf[i * 8 + 3] << 8) | buf[i * 8 + 2]);
touch_apply_rotation(&x, &y);
switch (i) {
case 0:
touch_point_t.x1 = x;
touch_point_t.y1 = y;
break;
case 1:
touch_point_t.x2 = x;
touch_point_t.y2 = y;
break;
case 2:
touch_point_t.x3 = x;
touch_point_t.y3 = y;
break;
case 3:
touch_point_t.x4 = x;
touch_point_t.y4 = y;
break;
case 4:
touch_point_t.x5 = x;
touch_point_t.y5 = y;
break;
default:
break;
}
}
// xil_printf("x1:%d y1:%d\\n", touch_point_t.x1, touch_point_t.y1);
// xil_printf("x2:%d y2:%d\\n", touch_point_t.x2, touch_point_t.y2);
// xil_printf("x3:%d y3:%d\\n", touch_point_t.x3, touch_point_t.y3);
// xil_printf("x4:%d y4:%d\\n", touch_point_t.x4, touch_point_t.y4);
// xil_printf("x5:%d y5:%d\\n", touch_point_t.x5, touch_point_t.y5);
}
// Clear touch status
status = 0x0;
gt911_write_reg(0x814E, &status, 1);
touch_point_t.touch_count=touch_num;
}
#ifndef _GT911_HPP_
#define _GT911_HPP_
#include "iic.hpp"
#include "gpio.hpp"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"
#define GT911_ADDRESS (0xBA>>1)
#define TOUCH_WIDTH 1024
#define TOUCH_HEIGHT 600
#define TOUCH_DEBOUNCE_MS 10
enum touch_revolve_degree_t {
TOUCH_REVOLVE_0,
TOUCH_REVOLVE_90,
TOUCH_REVOLVE_180,
TOUCH_REVOLVE_270,
};
typedef struct {
uint16_t x1;
uint16_t y1;
uint16_t x2;
uint16_t y2;
uint16_t x3;
uint16_t y3;
uint16_t x4;
uint16_t y4;
uint16_t x5;
uint16_t y5;
uint8_t touch_count;
} touch_point;
static void gt911_write_reg(uint16_t addr, uint8_t *buf, int len);
static void gt911_read_reg(uint16_t addr, uint8_t *buf, int len);
void init_touch(touch_revolve_degree_t degree);//need freertos
void read_touch();
#endif
这里就很简单了
void my_input_read(lv_indev_t * indev, lv_indev_data_t*data)
{
read_touch();
if(touch_point_t.touch_count>=1) {
data->state = LV_INDEV_STATE_PRESSED;
data->point.x = touch_point_t.x1;
data->point.y = touch_point_t.y1;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
5.0 文件打包,未展示的代码已经全部压缩供大家参考
百度网盘的资源
转接板PCB
总结
最后跑分如下,大部分可以稳定60帧.瓶颈在render速度.另外我也在b站看到不少stm32h7的跑分(800×480显示屏),好像轻轻松松就几百帧率.可能我这个显示屏确实比较大(1024×600)想破脑袋也不知道怎么再次优化zynq帧率了,不知道广大网友还有什么妙计.


