本帖最后由 LINUX课程 于 2024-9-11 11:13 编辑
软件版本:vitis2021.1(vivado2021.1) 操作系统:WIN10 64bit 硬件平台:适用XILINX Z7/ZU系列FPGA
1 简介 本章节实现了一个带触控的lcd显示器。实现的功能有framebuffer和lcd触控,输出画面依靠vdma进行显示,在此之上编写qt应用程序实现简单的触控功能。注意此处的触控指的是实现input子系统,而非系统界面可触控。 2 系统框图 3 方案介绍 本方案用到了三个驱动,分别是i2c驱动用于触控功能,vdma驱动用于画面输出,pwm驱动用于屏幕背光亮度的控制。 - 显示屏使用了gt9157芯片,可以通过i2c来进行数据交互,需要作为input子系统来使用。
- vdma驱动需要实现framebuffer功能。
上述驱动完成后,qt程序可以直接使用pwm的class来控制。 - class的控制方法位于/sys/class/backlight/backlight/brightness。
- 实现触控按钮功能。
4 方案搭建 4.1 vivado工程解析 此处不对vivado工程的搭建作详细解释,若想查看vivado搭建请移步FPGA部分课程。接下来将从Linux开发的角度分析vivado内需要注意的几个地方。 1:BD图 看到右侧部分的管脚: lcd_*:LCD屏幕显示接口 lcd_pwm:背光亮度pwm接口(本demo无效) gpio_rtl:触控的控制管脚 lcd_iic:触控芯片与Linux通信 2:查看寄存器地址 此处可以看到需要用到的寄存器地址,vitis导出的设备树会自动生成,所以看一下就行。 4.2 设备树解析 非常不建议初学者自己修改设备树,请使用demo中自带的设备树操作,若能理解设备树的含义与作用可自行修改。 1:vitis生成pl.dtsi PWM_0节点需要修改,其他节点都无需修改: 通过vitis生成的设备树如下: 需要修改为下图: 该设备树对应的驱动位于uisrc-lab-xlnx/sources/kernel/drivers/pwm/tcpwm.c。相较之前,设备树的修改主要有: - 设备树节点名称的变更。
- compatible的修改,此处用于驱动匹配。必须修改,否则无法正常显示。
- 添加pwm-cells的参数。
2:添加LCD显示的节点 该设备树对应的驱动位于/uisrc-lab-xlnx/sources/kernel/drivers/video/fbdev/vdmafb.c。dmas需要设置一下vdma的地址,具体看上方的axi_vdma_0节点。由于本工程使用了3缓存VDMA所以park必须设置为1。 3:添加触控的节点 该设备树对应的驱动位于/uisrc-lab-xlnx/sources/kernel/drivers/input/touchscreen/gt9xx。直接添加在根节点下,可与zynqmp-mzux.dts 文件中的i2c0节点共存。 4:添加背光节点 添加在/节点下,作用是设定背光等级,依次是从暗至明。(本demo无效) 4.3 构建系统 1:从vivado到vitis生成所需文件 若不清楚如何搭建,请复习Linux基础篇内第四章的内容,此处不再赘述。若想快速验证,在本课对应的demo中有工程已编译好可直接使用。 boot:启动文件,使用boot文件替换boot分区,即可搭配任意文件系统运行程序。 QT:pc端的qt接收与展示波形工程,文件夹内有两个文件,一个QT工程的文件夹为QT的工程,另一个qt_play.tar.gz的文件可解压到文件系统内,待系统启动时直接运行。 soc_dts、soc_hw、soc_prj、soc_sdk:移植系统所需工程,分别为设备树、硬件描述文件、vivado工程和vitis工程。若还不熟悉这些文件,请先学习Linux基础第四章的内容。 2:拷贝文件 以往的开发包,需要手动设置好5个文件,分别是fsbl.elf、system_wrapper.bit和kernel、uboot的设备树(设备树在demo中已经提供)。在新版本的开发包中可以无须手动改名,放置到指定路径即可。若不清楚本段的内容表述,请先重复Linux基础篇内第四章的内容,熟悉基础步骤。 将四个文件放置到/uisrc-lab-xlnx/boards/mz7x/ubuntu/output/files指定位置。其中fsbl.elf、system_wrapper.bit两个文件直接放在目录下,而kernel和uboot的设备树要放在对应的文件夹内。注意kernel-dts文件夹内的设备树支持include,所以可以放入多个文件,但是uboot-dts内设备树不支持include,只能支持名为zynq-mz7x.dts的设备树。 3:修改驱动文件 需要修改的驱动文件位于/home/uisrc/uisrc-lab-xlnx/sources/kernel/drivers/video/fbdev/vdmafb.c 原有驱动设置: 修改分辨率后: 注意:在做其他章节实验时,需要把此处改回原样。 4:构建所需系统 首先source环境变量,先前往以下路径/uisrc-lab-xlnx/scripts: 运行命令: source mz7xcfg.sh 用来设置环境变量 move_files.sh 将刚才我们拷贝的文件重命名并拷贝到对应的位置 make_uboot.sh 编译uboot make_kernel.sh 编译kernel create_image.sh 生成启动文件 紧接着插入sd卡,并连接到虚拟机内,继续输入命令: make_parted.sh 给sd卡分区,先输入sdb,再输入y deploy_image.sh 烧录系统 7100 SD卡启动,参考第一章第5节。 4:交叉编译qt 将uisrc-lab-qt20220601.tar.gz拷贝到虚拟机内/home/uisrc路径下,使用tar zxvf uisrc-lab-qt20220601.tar.gz命令解压,一定要确保解压在/home/uisrc路径下,否则会出现错误。 进入/uisrc-lab-qt/scripts路径,右键在此处打开终端,输入命令: source qt_cfg.sh 设置环境变量 进入/uisrc-lab-qt/applications路径,把本方案demo自带的qt demo拷贝进来,位于demo文件夹/QT/QT工程/mp_test: 将HelloQt中的build_arm_app.sh复制一份到mp_test中去: 打开刚才source好的终端,输入命令: cd ../applications/mp_test/ cd到指定路径 ./build_arm_app.sh 编译qt工程 此文件就是编译出来的文件: 将编译好的文件和demo中qt工程内附带的1.jpg拷贝到/uisrc-lab-qt/applications/qt_play中: 修改该文件夹下,run_arm_app.sh文件的内容,使用记事本打开该文件,将最后一行的名称改为我们编译出来文件的名称: 将qt_play整个文件夹拷贝到/media/uisrc/rootfs/home/uisrc里,这步的目的是将做好的程序放到文件系统内,开发板上电登录后即可运行: 弹出sd卡: 7100FC 系统烧录部分参考第一章第5节。从qspi启动sd卡。 5 驱动应用分析 5.1 pwm驱动 驱动位于/uisrc-lab-xlnx/sources/kernel/drivers/pwm/tcpwm.c 1:平台驱动 - static struct platform_driver tcpwm_fun_device_driver = {
- .probe = tcpwm_fun_probe,
- .remove = tcpwm_fun_remove,
- .driver = {
- .name = "tcpwm_func",
- .owner = THIS_MODULE,
- .of_match_table = of_match_ptr(tcpwm_fun_of_match),
- }};
复制代码从平台驱动结构体,我们可以看到tcpwm_fun_probe为平台驱动注册函数,tcpwm_fun_remove为平台驱动注销函数,从tcpwm_fun_of_match可以找到驱动匹配名称,我们下节来看。 2:match函数 - static struct of_device_id tcpwm_fun_of_match[] = {
- {
- .compatible = "xlnx,PWM-1.0",
- },
- {},
- };
复制代码compatible字段展示了设备树匹配名称为xlnx,PWM-1.0,这也就是为什么在4.2.2.1中需要修改设备树的compatible字段。 3:平台驱动注册函数 - static int tcpwm_fun_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct msxbo_pwm_chip *pc = dev_get_platdata(dev);
- struct resource *res;
- void __iomem *base;
- int ret = 0;
- if (!pc)
- {
- pc = devm_kzalloc(dev, sizeof(struct msxbo_pwm_chip), GFP_KERNEL);
- if (!pc)
- return -ENOMEM;
- platform_set_drvdata(pdev, pc);
- }
- #if 1
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- base = devm_ioremap_resource(dev, res);
- if (IS_ERR(base))
- return PTR_ERR(base);
- pc->pwm_regmap = devm_regmap_init_mmio(dev, base,
- &pwm_regmap_config);
- #endif
- pc->chip.dev = &pdev->dev;
- pc->chip.ops = &msxbo_pwm_ops;
- pc->chip.of_xlate = of_pwm_xlate_with_flags;
- pc->chip.of_pwm_n_cells = 3;
- pc->chip.base = -1;
- pc->chip.npwm = 1;
- ret = pwmchip_add(&pc->chip);
- if (ret < 0)
- {
- dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
- return ret;
- }
- regmap_write(pc->pwm_regmap, PWM_AXI_PERIOD_REG_OFFSET, PERIOD_CLOCK_NUM);
- pm_runtime_enable(&pdev->dev);
- return ret;
- }
复制代码这个函数主要是用来向内核注册pwm设备。 行9~16,首先为需要注册的结构体申请一段内存空间。 行18,从设备树获取相关资源。 行19~21,申请并映射出基地址。 行23~24,映射寄存器地址。 行27~32,初始化注册pwm结构体的各项配置。 行34,注册pwm设备。 3:pwm操作集合 - static const struct pwm_ops msxbo_pwm_ops = {
- .config = msxbo_pwm_config,
- .enable = msxbo_pwm_enable,
- .disable = msxbo_pwm_disable,
- .owner = THIS_MODULE,
- };
复制代码行2,设置pwm占空比。 行3,使能pwm。 行4,关闭pwm。 5.2 vdmafb驱动 驱动位于/uisrc-lab-xlnx/sources/kernel/drivers/video/fbdev/vdmafb.c 1:平台驱动 - static struct platform_driver vdmafb_driver = {
- .probe = vdmafb_probe,
- .remove = vdmafb_remove,
- .driver = {
- .name = "vdmafb_fb",
- .of_match_table = vdmafb_match,
- }};
复制代码行2,驱动注册函数。 行3,驱动注销函数。 行6,设备树匹配函数。 2:设备树匹配 - static struct of_device_id vdmafb_match[] = {
- {
- .compatible = "topic,vdma-fb",
- },
- {},
- };
复制代码对应了4.2.2中对设备树的修改。 3:驱动注册 - static int vdmafb_probe(struct platform_device *pdev)
- {
- int ret = 0;
- struct vdmafb_dev *fbdev;
- int fbsize;
- fbdev = devm_kzalloc(&pdev->dev, sizeof(*fbdev), GFP_KERNEL);
- if (!fbdev)
- return -ENOMEM;
- platform_set_drvdata(pdev, fbdev);
- fbdev->info.fbops = &vdmafb_ops;
- fbdev->info.device = &pdev->dev;
- fbdev->info.par = fbdev;
- fbdev->dma_template = devm_kzalloc(&pdev->dev,
- sizeof(struct dma_interleaved_template) +
- sizeof(struct data_chunk),
- GFP_KERNEL);
- if (!fbdev->dma_template)
- return -ENOMEM;
- vdmafb_init_var(fbdev, pdev);
- vdmafb_init_fix(fbdev);
- /* Allocate framebuffer memory */
- fbsize = fbdev->info.fix.smem_len;
- fbdev->fb_virt = dma_alloc_coherent(&pdev->dev, PAGE_ALIGN(fbsize),
- &fbdev->fb_phys, GFP_KERNEL);
- if (!fbdev->fb_virt)
- {
- dev_err(&pdev->dev,
- "Frame buffer memory allocation failed\n");
- return -ENOMEM;
- }
- fbdev->info.fix.smem_start = fbdev->fb_phys;
- fbdev->info.screen_base = fbdev->fb_virt;
- fbdev->info.pseudo_palette = fbdev->pseudo_palette;
- /* Clear framebuffer */
- memset_io(fbdev->fb_virt, 0, fbsize);
- fbdev->dma = dma_request_chan(&pdev->dev, "axivdma");
- if (IS_ERR_OR_NULL(fbdev->dma))
- {
- ret = PTR_ERR(fbdev->dma);
- if ((ret != -ENOENT) || (ret != -EPROBE_DEFER))
- {
- dev_err(&pdev->dev, "Failed to allocate DMA channel (%d).\n", ret);
- return ret;
- }
- goto err_dma_free;
- }
- ret = of_property_read_u32(pdev->dev.of_node, "park", &fbdev->park);
- if (ret < 0)
- {
- fbdev->park = 1;
- }
- /* Setup and enable the framebuffer */
- vdmafb_setupfb(fbdev);
- ret = fb_alloc_cmap(&fbdev->info.cmap, 256, 0);
- if (ret)
- {
- dev_err(&pdev->dev, "fb_alloc_cmap failed\n");
- }
- /* Register framebuffer */
- ret = register_framebuffer(&fbdev->info);
- if (ret)
- {
- dev_err(&pdev->dev, "Framebuffer registration failed\n");
- goto err_channel_free;
- }
- return 0;
- err_channel_free:
- dma_release_channel(fbdev->dma);
- err_dma_free:
- dma_free_coherent(&pdev->dev, PAGE_ALIGN(fbsize), fbdev->fb_virt,
- fbdev->fb_phys);
- return ret;
- }
复制代码行7~9,申请内存空间。 行11,将fbdev保存到pdev中,防止数据丢失。 行13~15,设置fb设备的操作集合与设备等。 行17~22,申请临时dma内存空间。 行24~25,设置显示屏的参数。 行28~39,申请fb内容的内存空间。 行42,将新申请的内存空间初始化。 行44~54,向内核申请一个dma通道 行56~60,读取设备树的park字段,确定是praking模式还是circular模式。 行63,设置设置fb设备的dma相关配置。 行72~77,向内核注册fb设备。 6 方案演示
6.1 硬件连线 1:板卡部分 2:屏幕部分 6.1 程序测试 1:上电并串口登录 账户:uisrc,密码:root。 此时屏幕已显示开机登录命令行界面。 2:运行qt 输入命令: cd qt_play/ 进入qt程序文件夹 sudo ./run_arm_app.sh 运行qt程序,输入密码:root 3:观察qt程序 屏幕上的加号减号可触控。 |