当前位置: 首页 > news >正文

Linux LCD 驱动实验

        LCD 是很常用的一个外设,在裸机篇中我们讲解了如何编写 LCD 裸机驱动,在 Linux
LCD 的使用更加广泛,在搭配 QT 这样的 GUI 库下可以制作出非常精美的 UI 界面。本章我们
就来学习一下如何在 Linux 下驱动 LCD 屏幕。

1 Linux LCD 驱动简析

1.1 Framebuffer 设备

先来回顾一下裸机的时候 LCD 驱动是怎么编写的,裸机 LCD 驱动编写流程如下:
①、初始化 I.MX6U eLCDIF 控制器,重点是 LCD 屏幕宽(width)、高(height)hspw
hbphfpvspwvbp vfp 等信息。
②、初始化 LCD 像素时钟。
③、设置 RGBLCD 显存。
④、应用程序直接通过操作显存来操作 LCD,实现在 LCD 上显示字符、图片等信息。
Linux 中应用程序最终也是通过操作 RGB LCD 的显存来实现在 LCD 上显示字符、图片
等信息。在裸机中我们可以随意的分配显存,但是在 Linux 系统中内存的管理很严格,显存是
需要申请的,不是你想用就能用的。而且因为虚拟内存的存在,驱动程序设置的显存和应用程
序访问的显存要是同一片物理内存。
为了解决上述问题,Framebuffer 诞生了, Framebuffer 翻译过来就是帧缓冲,简称 fb,因
此大家在以后的 Linux 学习中见到“Framebuffer”或者“fb”的话第一反应应该想到 RGBLCD
或者显示设备。fb 是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一
fb 设备,当我们编写好 LCD 驱动以后会生成一个名为/dev/fbX(X=0~n)的设备,应用程序通
过访问/dev/fbX 这个设备就可以访问 LCDNXP 官方的 Linux 内核默认已经开启了 LCD 驱动,
因此我们是可以看到/dev/fb0 这样一个设备,如图

        /dev/fb0 就是 LCD 对应的设备文件,/dev/fb0 是个字符设备,因此肯定有
file_operations 操作集,fb file_operations 操作集定义在 drivers/video/fbdev/core/fbmem.c 文件 中,如下所示:
1495 static const struct file_operations fb_fops = {
1496     .owner = THIS_MODULE,
1497     .read = fb_read,
1498     .write = fb_write,
1499     .unlocked_ioctl = fb_ioctl,
1500 #ifdef CONFIG_COMPAT
1501     .compat_ioctl = fb_compat_ioctl,
1502 #endif
1503     .mmap = fb_mmap,
1504     .open = fb_open,
1505     .release = fb_release,
1506 #ifdef HAVE_ARCH_FB_UNMAPPED_AREA
1507     .get_unmapped_area = get_fb_unmapped_area,
1508 #endif
1509 #ifdef CONFIG_FB_DEFERRED_IO
1510     .fsync = fb_deferred_io_fsync,
1511 #endif
1512     .llseek = default_llseek,
1513 };
        关于 fb 的详细处理过程就不去深究了,本章我们的重点是驱动起来 ALPHA 开发板上的
LCD

1.2 LCD 驱动简析

LCD 裸机例程主要分两部分:
①、获取 LCD 的屏幕参数。
②、根据屏幕参数信息来初始化 eLCDIF 接口控制器。
不同分辨率的 LCD 屏幕其 eLCDIF 控制器驱动代码都是一样的,只需要修改好对应的屏
幕参数即可。屏幕参数信息属于屏幕设备信息内容,这些肯定是要放到设备树中的,因此我们
本章实验的主要工作就是修改设备树,NXP 官方的设备树已经添加了 LCD 设备节点,只是此
节点的 LCD 屏幕信息是针对 NXP 官方 EVK 开发板所使用的 4.3 480*272 编写的,我们需
要将其改为我们所使用的屏幕参数。
我们简单看一下 NXP 官方编写的 Linux 下的 LCD 驱动,打开 imx6ull.dtsi,然后找到 lcdif
节点内容,如下所示:
1 lcdif: lcdif@021c8000 {
2     compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
3     reg = <0x021c8000 0x4000>;
4     interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
5     clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
6              <&clks IMX6UL_CLK_LCDIF_APB>,
7              <&clks IMX6UL_CLK_DUMMY>;
8     clock-names = "pix", "axi", "disp_axi";
9     status = "disabled";
10 };
         lcdif 节点信息是所有使用 I.MX6ULL 芯片的板子所共有的,并不是 完整的 lcdif 节点信息。像屏幕参数这些需要根据不同的硬件平台去添加,比如向 imx6ull-alientek-emmc.dts 中的 lcdif 节点添加其他的属性信息。从示例代码可以看出 lcdif 节点 的 compatible 属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,因此在 Linux 源码中搜索这两个 字符串即可找到 I.MX6ULL LCD 驱动文件,这个文件为 drivers/video/fbdev/mxsfb.cmxsfb.c 就是 I.MX6ULL LCD 驱动文件,在此文件中找到如下内容:
1362 static const struct of_device_id mxsfb_dt_ids[] = {
1363     { .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
1364     { .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
1365     { /* sentinel */ }
1366 };......
1625 static struct platform_driver mxsfb_driver = {
1626     .probe = mxsfb_probe,
1627     .remove = mxsfb_remove,
1628     .shutdown = mxsfb_shutdown,
1629     .id_table = mxsfb_devtype,
1630     .driver = {
1631         .name = DRIVER_NAME,
1632         .of_match_table = mxsfb_dt_ids,
1633         .pm = &mxsfb_pm_ops,
1634     },
1635 };
1636 
1637 module_platform_driver(mxsfb_driver);
        这是一个标准的 platform 驱动,当驱动和设备匹配以后 mxsfb_probe 函数就会执行。在看 mxsfb_probe 函数之前我们先简单了解一下 Linux 下 Framebuffer 驱动的编写流程,Linux 内核将所有的 Framebuffer 抽象为一个叫做 fb_info 的结构 体,fb_info 结构体包含了 Framebuffer 设备的完整属性和操作集合,因此每一个 Framebuffer 设 备都必须有一个 fb_info。换言之就是,LCD 的驱动就是构建 fb_info,并且向系统注册 fb_info 的过程。fb_info 结构体定义在 include/linux/fb.h 文件里面,内容如下(省略掉条件编译)
448 struct fb_info {
449     atomic_t count;
450     int node;
451     int flags;
452     struct mutex lock;           /* 互斥锁 */
453     struct mutex mm_lock;        /* 互斥锁,用于 fb_mmap 和 smem_*域 */
454     struct fb_var_screeninfo var; /* 当前可变参数 */
455     struct fb_fix_screeninfo fix; /* 当前固定参数 */
456     struct fb_monspecs monspecs;  /* 当前显示器特性 */
457     struct work_struct queue;    /* 帧缓冲事件队列 */
458     struct fb_pixmap pixmap;     /* 图像硬件映射 */
459     struct fb_pixmap sprite;     /* 光标硬件映射 */
460     struct fb_cmap cmap;         /* 当前调色板 */
461     struct list_head modelist;   /* 当前模式列表 */
462     struct fb_videomode *mode;   /* 当前视频模式 */
463 
464 #ifdef CONFIG_FB_BACKLIGHT       /* 如果 LCD 支持背光的话 */
465     /* assigned backlight device */
466     /* set before framebuffer registration,
467        remove after unregister */
468     struct backlight_device *bl_dev; /* 背光设备 */
469 
470     /* Backlight level curve */
471     struct mutex bl_curve_mutex;
472     u8 bl_curve[FB_BACKLIGHT_LEVELS];
473 #endif
...
479     struct fb_ops *fbops;       /* 帧缓冲操作函数集 */
480     struct device *device;      /* 父设备 */
481     struct device *dev;         /* 当前 fb 设备 */
482     int class_flag;             /* 私有 sysfs 标志 */
...
486     char __iomem *screen_base;  /* 虚拟内存基地址(屏幕显存) */
487     unsigned long screen_size;  /* 虚拟内存大小(屏幕显存大小) */
488     void *pseudo_palette;       /* 伪 16 位调色板 */
...
507 };
fb_info 结构体的成员变量很多,我们重点关注 varfixfbopsscreen_basescreen_size
pseudo_palettemxsfb_probe 函数的主要工作内容为:
①、申请 fb_info
②、初始化 fb_info 结构体中的各个成员变量。
③、初始化 eLCDIF 控制器。
④、使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_inforegister_framebuffer
函数原型如下:
int register_framebuffer(struct fb_info *fb_info)
接下来我们简单看一下 mxsfb_probe 函数,函数内容如下(有缩减)
1369 static int mxsfb_probe(struct platform_device *pdev)
1370 {
1371     const struct of_device_id *of_id =
1372         of_match_device(mxsfb_dt_ids, &pdev->dev);
1373     struct resource *res;
1374     struct mxsfb_info *host;
1375     struct fb_info *fb_info;
1376     struct pinctrl *pinctrl;
1377     int irq = platform_get_irq(pdev, 0);
1378     int gpio, ret;
1379
......
1394
1395     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1396     if (!res) {
1397         dev_err(&pdev->dev, "Cannot get memory IO resource\n");
1398         return -ENODEV;
1399     }
1400
1401     host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
1402     if (!host) {
1403         dev_err(&pdev->dev, "Failed to allocate IO resource\n");
1404         return -ENOMEM;
1405     }
1406
1407     fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
1408     if (!fb_info) {
1409         dev_err(&pdev->dev, "Failed to allocate fbdev\n");
1410         devm_kfree(&pdev->dev, host);
1411         return -ENOMEM;
1412     }
1413     host->fb_info = fb_info;
1414     fb_info->par = host;
1415
1416     ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0,
1417               dev_name(&pdev->dev), host);
1418     if (ret) {
1419         dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n", irq, ret);
1420         ret = -ENODEV;
1421         goto fb_release;
1422     }
1423
1424     host->base = devm_ioremap_resource(&pdev->dev, res);
1425     if (IS_ERR(host->base)) {
1426         dev_err(&pdev->dev, "ioremap failed\n");
1427         ret = PTR_ERR(host->base);
1428         goto fb_release;
1429     }
......
1461
1462     fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16, GFP_KERNEL);
1463     if (!fb_info->pseudo_palette) {
1464         ret = -ENOMEM;
1465         goto fb_release;
1466     }
1467
1468     INIT_LIST_HEAD(&fb_info->modelist);
1469
1470     pm_runtime_enable(&host->pdev->dev);
1471
1472     ret = mxsfb_init_fbinfo(host);
1473     if (ret != 0)
1474         goto fb_pm_runtime_disable;
1475
1476     mxsfb_dispdrv_init(pdev, fb_info);
1477
1478     if (!host->dispdrv) {
1479         pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
1480         if (IS_ERR(pinctrl)) {
1481             ret = PTR_ERR(pinctrl);
1482             goto fb_pm_runtime_disable;
1483         }
1484     }
1485
1486     if (!host->enabled) {
1487         writel(0, host->base + LCDC_CTRL);
1488         mxsfb_set_par(fb_info);
1489         mxsfb_enable_controller(fb_info);
1490         pm_runtime_get_sync(&host->pdev->dev);
1491     }
1492
1493     ret = register_framebuffer(fb_info);
1494     if (ret != 0) {
1495         dev_err(&pdev->dev, "Failed to register framebuffer\n");
1496         goto fb_destroy;
1497     }
......
1525     return ret;
1526 }
        第 1374 行,host 结构体指针变量,表示 I.MX6ULL LCD 的主控接口,mxsfb_info 结构
体是 NXP 定义的针对 I.MX 系列 SOC Framebuffer 设备结构体。也就是我们前面一直说的设
备结构体,此结构体包含了 I.MX 系列 SOC Framebuffer 设备详细信息,比如时钟、eLCDIF
控制器寄存器基地址、fb_info 等。
        第 1395 行,从设备树中获取 eLCDIF 接口控制器的寄存器首地址,设备树中 lcdif 节点已
经设置了 eLCDIF 寄存器首地址为 0X021C8000,因此 res=0X021C8000
        第 1401 行,给 host 申请内存,host mxsfb_info 类型结构体指针。
        第 1407 行,给 fb_info 申请内存,也就是申请 fb_info
        第 1413~1414 行,设置 host fb_info 成员变量为 fb_info,设置 fb_info par 成员变量为
host。通过这一步就将前面申请的 host fb_info 联系在了一起。
        第 1416 行,申请中断,中断服务函数为 mxsfb_irq_handler
        第 1425 行,对从设备树中获取到的寄存器首地址(res)进行内存映射,得到虚拟地址,并保
存到 host base 成员变量。因此通过访问 host base 成员即可访问 I.MX6ULL 的整个 eLCDIF
寄存器。其实在 mxsfb.c 中已经定义了 eLCDIF 各个寄存器相比于基地址的偏移值,如下所示:
67 #define LCDC_CTRL 0x00
68 #define LCDC_CTRL1 0x10
69 #define LCDC_V4_CTRL2 0x20
70 #define LCDC_V3_TRANSFER_COUNT 0x20
71 #define LCDC_V4_TRANSFER_COUNT 0x30
......
89 #define LCDC_V4_DEBUG0 0x1d0
90 #define LCDC_V3_DEBUG0 0x1f0
        大家可以对比着《I.MX6ULL 参考手册》中的 eLCDIF 章节检查一下代码中的这些寄存器有没有错误。
        继续回到示例代码中的 mxsfb_probe 函数,第1462 行,给 fb_info中的 pseudo_palette
申请内存。
        第 1473 行,调用 mxsfb_init_fbinfo 函数初始化 fb_info,重点是 fb_info varfixfbops
screen_base screen_size。其中 fbops Framebuffer 设备的操作集,NXP 提供的 fbops
mxsfb_ops,内容如下:
987 static struct fb_ops mxsfb_ops = {
988 .owner = THIS_MODULE,
989 .fb_check_var = mxsfb_check_var,
990 .fb_set_par = mxsfb_set_par,
991 .fb_setcolreg = mxsfb_setcolreg,
992 .fb_ioctl = mxsfb_ioctl,
993 .fb_blank = mxsfb_blank,
994 .fb_pan_display = mxsfb_pan_display,
995 .fb_mmap = mxsfb_mmap,
996 .fb_fillrect = cfb_fillrect,
997 .fb_copyarea = cfb_copyarea,
998 .fb_imageblit = cfb_imageblit,
999 };
        关于 mxsfb_ops 里面的各个操作函数这里就不去详解的介绍了。mxsfb_init_fbinfo 函数通过
调用 mxsfb_init_fbinfo_dt 函数从设备树中获取到 LCD 的各个参数信息。最后,mxsfb_init_fbinfo
函数会调用 mxsfb_map_videomem 函数申请 LCD 的帧缓冲内存(也就是显存)
        第 1489~1490 行,设置 eLCDIF 控制器的相应寄存器。
        第 1494 行,最后调用 register_framebuffer 函数向 Linux 内核注册 fb_info
mxsfb.c 文件很大,还有一些其他的重要函数,比如 mxsfb_removemxsfb_shutdown 等,
这里我们就简单的介绍了一下 mxsfb_probe 函数,至于其他的函数大家自行查阅。

2 硬件原理图分析

3 LCD 驱动程序编写

        前面已经说了,6ULL eLCDIF 接口驱动程序 NXP 已经编写好了,因此 LCD 驱动部分
我们不需要去修改。我们需要做的就是按照所使用的 LCD 来修改设备树。重点要注意三个地
方:
①、LCD 所使用的 IO 配置。
②、LCD 屏幕节点修改,修改相应的属性值,换成我们所使用的 LCD 屏幕参数。
③、LCD 背光节点信息修改,要根据实际所使用的背光 IO 来修改相应的设备节点信息。
接下来我们依次来看一下上面这两个节点改如何去修改:

1LCD 屏幕 IO 配置

        首先要检查一下设备树中 LCD 所使用的 IO 配置,这个其实 NXP 都已经给我们写好了,
不需要修改,不过我们还是要看一下。打开 imx6ull-alientek-emmc.dts 文件,在 iomuxc 节点中
找到如下内容:
1 pinctrl_lcdif_dat: lcdifdatgrp {
2 fsl,pins = <
3 MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79
4 MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79
5 MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x79
6 MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x79
7 MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x79
8 MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x79
9 MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x79
10 MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x79
11 MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x79
12 MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x79
13 MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x79
14 MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x79
15 MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x79
16 MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x79
17 MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x79
18 MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x79
19 MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x79
20 MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x79
21 MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x79
22 MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x79
23 MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x79
24 MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x79
25 MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x79
26 MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x79
27 >;
28 };
29
30 pinctrl_lcdif_ctrl: lcdifctrlgrp {
31 fsl,pins = <
32 MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79
33 MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79
34 MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79
35 MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79
36 >;
37 pinctrl_pwm1: pwm1grp {
38 fsl,pins = <
39 MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0
40 >;
41 };
        第 2~27 行,子节点 pinctrl_lcdif_dat,为 RGB LCD 24 根数据线配置项。
        第 30~36 行,子节点 pinctrl_lcdif_ctrlRGB LCD 4 根控制线配置项,包括 CLK
ENABLEVSYNC HSYNC
        第 37~40 行,子节点 pinctrl_pwm1LCD 背光 PWM 引脚配置项。这个引脚要根据实际
情况设置,这里我们建议大家在以后的学习或工作中,LCD 的背光 IO 尽量和半导体厂商的官
方开发板一致。
        注意示例代码中默认将 LCD 的电气属性都设置为 0X79,这里将其都改为 0X49
也就是将 LCD 相关 IO 的驱动能力改为 R0/1,也就是降低 LCD 相关 IO 的驱动能力。因为前
面已经说了,正点原子的 ALPHA 开发板上的 LCD 接口用了三个 SGM3157 模拟开关,为了防
止模拟开关影响到网络,因此这里需要降低 LCD 数据线的驱动能力,如果你所使用的板子没
有用到模拟开关那么就不需要将 0X79 改为 0X49

2LCD 屏幕参数节点信息修改

继续在 imx6ull-alientek-emmc.dts 文件中找到 lcdif 节点,节点内容如下所示:1 &lcdif {2     pinctrl-names = "default";3     pinctrl-0 = <&pinctrl_lcdif_dat /* 使用到的 IO */4         &pinctrl_lcdif_ctrl5         &pinctrl_lcdif_reset>;6     display = <&display0>;7     status = "okay";8 9     display0: display { /* LCD 属性信息 */
10         bits-per-pixel = <16>; /* 一个像素占用几个 bit */
11         bus-width = <24>; /* 总线宽度 */
12
13         display-timings {
14             native-mode = <&timing0>; /* 时序信息 */
15             timing0: timing0 { 
16                 clock-frequency = <9200000>; /* LCD 像素时钟,单位 Hz */
17                 hactive = <480>; /* LCD X 轴像素个数 */
18                 vactive = <272>; /* LCD Y 轴像素个数 */
19                 hfront-porch = <8>; /* LCD hfp 参数 */
20                 hback-porch = <4>; /* LCD hbp 参数 */
21                 hsync-len = <41>; /* LCD hspw 参数 */
22                 vback-porch = <2>; /* LCD vbp 参数 */
23                 vfront-porch = <4>; /* LCD vfp 参数 */
24                 vsync-len = <10>; /* LCD vspw 参数 */
25
26                 hsync-active = <0>; /* hsync 数据线极性 */
27                 vsync-active = <0>; /* vsync 数据线极性 */
28                 de-active = <1>; /* de 数据线极性 */
29                 pixelclk-active = <0>; /* clk 数据线先极性 */
30             };
31         };
32     };
33 };
        向 imx6ull.dtsi 文件中的 lcdif 节点追加的内容,我们依次来看一下代码中的这些属性都是些什么含义。
        第 3 行,pinctrl-0 属性,LCD 所使用的 IO 信息,这里用到了 pinctrl_lcdif_datpinctrl_lcdif_ctrl 和 pinctrl_lcdif_reset 这三个 IO 相关的节点,前两个在示例代码中已经讲解了。 pinctrl_lcdif_reset 是 LCD 复位 IO 信息节点,正点原子的 I.MX6U-ALPHA 开发板的 LCD 没有 用到复位 IO,因此 pinctrl_lcdif_reset 可以删除掉。
        第 6 行,display 属性,指定 LCD 属性信息所在的子节点,这里为 display0,下面就是 display0子节点内容。
        第 9~32 行,display0 子节点,描述 LCD 的参数信息,第 10 行的 bits-per-pixel 属性用于指
明一个像素占用的 bit 数,默认为 16bit。本教程我们将 LCD 配置为 RGB888 模式,因此一个像
素点占用 24bitbits-per-pixel 属性要改为 24。第 11 行的 bus-width 属性用于设置数据线宽度,
因为要配置为 RGB888 模式,因此 bus-width 也要设置为 24
        第 13~30 行,这几行非常重要!因为这几行设置了 LCD 的时序参数信息,NXP 官方的 EVK
开发板使用了一个 4.3 寸的 480*272 屏幕,因此这里默认是按照 NXP 官方的那个屏幕参数设置
的。每一个属性的含义后面的注释已经写的很详细了,大家自己去看就行了,这些时序参数就
是我们重点要修改的,需要根据自己所使用的屏幕去修改。
这里以正点原子的 ATK7016(7 1024*600)屏幕为例,将 imx6ull-alientek-emmc.dts 文件中
lcdif 节点改为如下内容:
1 &lcdif {
2     pinctrl-names = "default";
3     pinctrl-0 = <&pinctrl_lcdif_dat /* 使用到的 IO */
4         &pinctrl_lcdif_ctrl>;
5     display = <&display0>;
6     status = "okay";
7 
8     display0: display { /* LCD 属性信息 */
9         bits-per-pixel = <24>; /* 一个像素占用 24bit */
10        bus-width = <24>; /* 总线宽度 */
11
12        display-timings {
13            native-mode = <&timing0>; /* 时序信息 */
14            timing0: timing0 { 
15                clock-frequency = <51200000>; /* LCD 像素时钟,单位 Hz */
16                hactive = <1024>; /* LCD X 轴像素个数 */
17                vactive = <600>; /* LCD Y 轴像素个数 */
18                hfront-porch = <160>; /* LCD hfp 参数 */
19                hback-porch = <140>; /* LCD hbp 参数 */
20                hsync-len = <20>; /* LCD hspw 参数 */
21                vback-porch = <20>; /* LCD vbp 参数 */
22                vfront-porch = <12>; /* LCD vfp 参数 */
23                vsync-len = <3>; /* LCD vspw 参数 */
24
25                hsync-active = <0>; /* hsync 数据线极性 */
26                vsync-active = <0>; /* vsync 数据线极性 */
27                de-active = <1>; /* de 数据线极性 */
28                pixelclk-active = <0>; /* clk 数据线先极性 */
29            };
30        };
31    };
32 };
        第 3 行,设置 LCD 屏幕所使用的 IO,删除掉原来的 pinctrl_lcdif_reset,因为没有用到屏
幕复位 IO,其他的 IO 不变。
        第 9 行,使用 RGB888 模式,所以一个像素点是 24bit
        第 15~23 行,ATK7016 屏幕时序参数,根据自己所使用的屏幕修改即可。

3LCD 屏幕背光节点信息

        正点原子的 LCD 接口背光控制 IO 连接到了 I.MX6U GPIO1_IO08 引脚上,GPIO1_IO08
复用为 PWM1_OUT,通过 PWM 信号来控制 LCD 屏幕背光的亮度,这个我们已经在第二十九
章详细的讲解过了。正点原子 I.MX6U-ALPHA 开发板的 LCD 背光引脚和 NXP 官方 EVK 开发
板的背光引脚一样,因此背光的设备树节点是不需要修改的,但是考虑到其他同学可能使用别
的开发板或者屏幕,LCD 背光引脚和 NXP 官方 EVK 开发板可能不同,因此我们还是来看一下
如何在设备树中添加背光节点信息。
        首先是 GPIO1_IO08 这个 IO 的配置,在 imx6ull-alientek-emmc.dts 中找到如下内容:
1 pinctrl_pwm1: pwm1grp {
2     fsl,pins = <
3     MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0
4     >;
5 };
        pinctrl_pwm1 节点就是 GPIO1_IO08 的配置节点,从第 3 行可以看出,设置 GPIO1_IO08
这个 IO 复用为 PWM1_OUT,并且设置电气属性值为 0x110b0
        LCD 背光要用到 PWM1,因此也要设置 PWM1 节点,在 imx6ull.dtsi 文件中找到如下内容:
1 pwm1: pwm@02080000 {
2     compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
3     reg = <0x02080000 0x4000>;
4     interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>;
5     clocks = <&clks IMX6UL_CLK_PWM1>,
6     <&clks IMX6UL_CLK_PWM1>;
7     clock-names = "ipg", "per";
8     #pwm-cells = <2>;
9 };
        imx6ull.dtsi 文件中的 pwm1 节点信息大家不要修改,如果要修改 pwm1 节点内容的话请在
imx6ull-alientek-emmc.dts 文件中修改。在整个 Linux 源码文件中搜索 compatible 属性的这两个
值即可找到 imx6ull pwm 驱动文件,imx6ull PWM 驱动文件为 drivers/pwm/pwm-imx.c
这里我们就不详细的去分析这个文件了。继续在 imx6ull-alientek-emmc.dts 文件中找到向 pwm1
追加的内容,如下所示:
1 &pwm1 {
2     pinctrl-names = "default";
3     pinctrl-0 = <&pinctrl_pwm1>;
4     status = "okay";
5 };
        第 3 行,设置 pwm1 所使用的 IO pinctrl_pwm1,也就是示例代码所定义的GPIO1_IO08 这个 IO
        第 4 行,将 status 设置为 okay。 如果背光用的其他 pwm 通道,比如 pwm2,那么就需要仿照示例代码的内容,向 pwm2 节点追加相应的内容。pwm 和相关的 IO 已经准备好了,但是 Linux 系统怎么知道 PWM1_OUT 就是控制 LCD背光的呢?因此我们还需要一个节点来将 LCD 背光和 PWM1_OUT 连接起来。这个节点就是 backlight backlight 节点描述可以参考
Documentation/devicetree/indings/video/backlight/pwm-backlight.txt 这个文档,此文档详细讲解了
        backlight 节点该如何去创建,这里大概总结一下:
①、节点名称要为“backlight”。
②、节点的 compatible 属性值要为“pwm-backlight”,因此可以通过在 Linux 内核中搜索
pwm-backlight ” 来 查 找 PWM 背 光 控 制 驱 动 程 序 , 这 个 驱 动 程 序 文 件 为
drivers/video/backlight/pwm_bl.c,感兴趣的可以去看一下这个驱动程序。
③、pwms属性用于描述背光所使用的PWM以及PWM频率,比如本章我们要使用的pwm1
pwm 频率设置为 200Hz(NXP 官方推荐设置)
④、brightness-levels 属性描述亮度级别,范围为 0~2550 表示 PWM 占空比为 0%,也就
是亮度最低,255 表示 100%占空比,也就是亮度最高。至于设置几级亮度,大家可以自行填写
此属性。
⑤、default-brightness-level 属性为默认亮度级别。
根据上述 5 点设置 backlight 节点,这个 NXP 已经给我们设置好了,大家在 imx6ull-alientek-emmc.dts 文件中找到如下内容:
1 backlight {
2     compatible = "pwm-backlight";
3     pwms = <&pwm1 0 5000000>;
4     brightness-levels = <0 4 8 16 32 64 128 255>;
5     default-brightness-level = <6>;
6     status = "okay";
7 };
        第 3 行,设置背光使用 pwm1PWM 频率为 200Hz
        第 4 行,设置背 8 级背光(0~7),分别为 048163264128255,对应占空比为
0%1.57%3.13%6.27%12.55%25.1%50.19%100%,如果嫌少的话可以自行添加一些其他的背光等级值。
        第 5 行,设置默认背光等级为 6,也就是 50.19%的亮度。
关于背光的设备树节点信息就讲到这里,整个的 LCD 设备树节点内容我们就讲完了,按照
这些节点内容配置自己的开发板即可。

4 运行测试

4.1 LCD 屏幕基本测试

1、编译新的设备树

2、使能 Linux logo 显示

        Linux 内核启动的时候可以选择显示小企鹅 logo,只要这个小企鹅 logo 显示没问题那么我
们的 LCD 驱动基本就工作正常了。这个 logo 显示是要配置的,不过 Linux 内核一般都会默认
开启 logo 显示,但是奔着学习的目的,我们还是来看一下如何使能 Linux logo 显示。打开 Linux
内核图形化配置界面,按下路径找到对应的配置项:

        这三个选项分别对应黑白、16 位、24 位色彩格式的 logo,我们把这三个都选中,都编译进 Linux 内核里面。设置好以后保存退出,重新编译 Linux 内核,编译完成以后使用新编译出来的 imx6ull-alientek-emmc.dtb zImage 镜像启动系统,如果 LCD 驱动工作正常的话就会在 LCD 屏幕左上角出现一个彩色的小企鹅 logo,屏幕背景色为黑色,如图所示:

4.2 设置 LCD 作为终端控制台

        我们一直使用SecureCRT作为Linux开发板终端,开发板通过串口和 SecureCRT进行通信。
现在我们已经驱动起来 LCD 了,所以可以设置 LCD 作为终端,也就是开发板使用自己的显示
设备作为自己的终端,然后接上键盘就可以直接在开发板上敲命令了,将 LCD 设置为终端控制
台的方法如下:
1、设置 uboot 中的 bootargs
        重启开发板,进入 Linux 命令行,重新设置 bootargs 参数的 console 内容,命令如下所示:
setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:
/home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:
off'
        注意红色字体部分设置 console,这里我们设置了两遍 console,第一次设置 console=tty1
也就是设置 LCD 屏幕为控制台,第二遍又设置 console=ttymxc0,115200,也就是设置串口也作
为控制台。相当于我们打开了两个 console,一个是 LCD,一个是串口,大家重启开发板就会发
LCD 和串口都会显示 Linux 启动 log 信息。但是此时我们还不能使用 LCD 作为终端进行交
互,因为我们的设置还未完成。
2、修改/etc/inittab 文件
        打开开发板根文件系统中的/etc/inittab 文件,在里面加入下面这一行:
tty1::askfirst:-/bin/sh

        修改完成以后保存/etc/inittab 并退出,然后重启开发板,重启以后开发板 LCD 屏幕最后一
行会显示下面一行语句:
Please press Enter to activate this console.
        上述提示语句说的是:按下回车键使能当前终端,我们在第五十八章已经将 I.MX6U-ALPHA 开发板上的 KEY 按键注册为了回车键,因此按下开发板上的 KEY 按键即可使能 LCD这个终端。当然了,大家也可以接上一个 USB 键盘,Linux 内核默认已经使能了 USB 键盘驱动了,因此可以直接使用 USB 键盘。
        至此,我们就拥有了两套终端,一个是基于串口的 SecureCRT,一个就是我们开发板的 LCD
屏幕,但是为了方便调试,我们以后还是以 SecureCRT 为主。我们可以通过下面这一行命令向
LCD 屏幕输出“hello linux!”
echo hello linux > /dev/tty1

4.3 LCD 背光调节

        背光设备树节点设置了 8 个等级的背光调节,可以设置为 0~7,我们可以通过设置背光等级来实现 LCD 背光亮度的调节,进入如下目录:
/sys/devices/platform/backlight/backlight/backlight

        brightness 表示当前亮度等级,max_bgigntness 表示最大亮度等级。当前这 两个文件内容如图

        可以看出,当前屏幕亮度等级为 6,根据前面的分析可以,这个是 50%亮度。 屏幕最大亮度等级为 7。如果我们要修改屏幕亮度,只需要向 brightness 写入需要设置的屏幕亮度等级即可。比如设置屏幕亮度等级为 7,那么可以使用如下命令:
echo 7 > brightness
        输入上述命令以后就会发现屏幕亮度增大了,如果设置 brightness 0 的话就会关闭 LCD
背光,屏幕就会熄灭。

4.4 LCD 自动关闭解决方法

        默认情况下 10 分钟以后 LCD 就会熄屏,这个并不是代码有问题,而是 Linux 内核设置的,
就和我们用手机或者电脑一样,一段时间不操作的话屏幕就会熄灭,以节省电能。解决这个问
题有多种方法,我们依次来看一下:
1、按键盘唤醒
最简单的就是按下回车键唤醒屏幕,我们在第 58 章将 I.MX6U-ALPHA 开发板上的 KEY
按键注册为了回车键,因此按下开发板上的 KEY 按键即可唤醒屏幕。如果开发板上没有按键的
话可以外接 USB 键盘,然后按下 USB 键盘上的回车键唤醒屏幕。
2、关闭 10 分钟熄屏功能
Linux 源码中找到 drivers/tty/vt/vt.c 这个文件,在此文件中找到 blankinterval 变量,如下
所示:
        blankinterval 变量控制着 LCD 关闭时间,默认是 10*60,也就是 10 分钟。将 blankinterval
的值改为 0 即可关闭 10 分钟熄屏的功能,修改完成以后需要重新编译 Linux 内核,得到新的
zImage,然后用新的 zImage 启动开发板。
3、编写一个 APP 来关闭熄屏功能
ubuntu 中新建一个名为 lcd_always_on.c 的文件,然后在里面输入如下所示内容:
1 #include <fcntl.h>
2 #include <stdio.h>
3 #include <sys/ioctl.h>
4 
5 
6 int main(int argc, char *argv[])
7 {
8 int fd;
9 fd = open("/dev/tty1", O_RDWR);
10 write(fd, "\033[9;0]", 8);
11 close(fd);
12 return 0;
13 }
        编译生成 lcd_always_on 以后将此可执行文件拷贝到开发板根文件系统的/usr/bin 目录中,
然后给予可执行权限。设置 lcd_always_on 这个软件为开机自启动,打开/etc/init.d/rcS,在此文
件最后面加入如下内容:
1 cd /usr/bin
2 ./lcd_always_on
3 cd ..
        修改完成以后保存/etc/init.d/rcS 文件,然后重启开发板即可。关于 Linux 下的 LCD 驱动
我们就讲到这里。
http://www.dtcms.com/a/578735.html

相关文章:

  • 1.8 Agent 构建与多轮对话逻辑设计:构建企业级智能对话系统
  • almalinux8 virtuoso基础安装包
  • 有关建设旅游网站的公司ui界面设计英文
  • 行业网站建设申请报告东莞公司注册多少钱
  • 内存操作函数
  • 小说网站开发多少钱免费动画模板素材网站
  • 鞍山怎么样做一个自己的网站深圳防疫措施优化调整
  • linux中安装MinIO
  • 怎么建设一个企业网站外国自适应企业网站
  • CVPR 2025论文分享|一种融合世界模型的4D驾驶场景重建框架DriveDreamer4D
  • 咸阳网站建设公司电话wap免费
  • 昆明响应式网站制作建站网址大全
  • 营销型网站建设的认识视频模板免费制作
  • 付费软件免费拿佛山网站优化包年
  • 关于Ai问答的ET7.2框架协程锁解读记录
  • 项目设计文档【示例】
  • Linux应用开发-11-消息队列
  • 基于遗传算法与非线性规划的混合优化算法在电力系统最优潮流中的实现
  • 高动态范围(HDR)图像文件格式OpenEXRLinux开发库介绍
  • 在门户网站做产品seowordpress主题英文改中文版
  • 百度助手手机下载网站链接优化
  • 商标注册查询官网网站wordpress怎么截图直接粘贴
  • 网站建设价格与方案如何让百度口碑收录自己的网站
  • 大模型原理之深度学习与神经网络入门
  • 学习前端记录(二)21-40
  • 电源完整性08-电容网络配置方法
  • 可以做防盗水印的网站山东手机版建站系统信息
  • 专门做音箱的网站浙江信息网查询系统
  • 论需求分析方法及应用
  • 网站顶部菜单下拉固定宜宾市珙县住房城乡建设网站