linux LCD驱动实验

2023-11-10


在 Linux 下LCD 的使用更加广泛,在搭配 QT 这样的 GUI 库下可以制作出非常精美的 UI 界面。

一、linux下LCD驱动解析

1.Framebuffer设备

先来回顾一下裸机的时候 LCD 驱动是怎么编写的,裸机 LCD 驱动编写流程如下:

①、初始化 I.MX6U 的 eLCDIF 控制器,重点是 LCD 屏幕宽(width)、高(height)、hspw、hbp、hfp、vspw、vbp 和 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 这个设备就可以访问 LCD。

NXP 官方的 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。

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-
luatao-emmc.dts 中的 lcdif 节点添加其他的属性信息。

从示例代码可以看出 lcdif 节点的 compatible 属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,因此在 Linux 源码中搜索这两个字符串即可找到 I.MX6ULL 的 LCD 驱动文件,这个文件为 drivers/video/fbdev/mxsfb.c,
mxsfb.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 结构体的成员变量很多,我们重点关注 var、fix、fbops、screen_base、screen_size和 pseudo_palette。mxsfb_probe 函数的主要工作内容为:

①、申请 fb_info。
②、初始化 fb_info 结构体中的各个成员变量。
③、初始化 eLCDIF 控制器。
④、使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。

register_framebuffer函数原型如下:

int register_framebuffer(struct fb_info *fb_info)

函数参数和返回值含义如下:

fb_info:需要上报的 fb_info。
返回值:0,成功;负值,失败。

接下来我们简单看一下 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
1420 error %d\n", irq, ret);
1421 ret = -ENODEV;
1422 goto fb_release;
1423 }
1424
1425 host->base = devm_ioremap_resource(&pdev->dev, res);
1426 if (IS_ERR(host->base)) {
1427 dev_err(&pdev->dev, "ioremap failed\n");
1428 ret = PTR_ERR(host->base);
1429 goto fb_release;
1430 }
	......
1461
1462 fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) *
1463 16, GFP_KERNEL);
1464 if (!fb_info->pseudo_palette) {
1465 ret = -ENOMEM;
1466 goto fb_release;
1467 }
1468
1469 INIT_LIST_HEAD(&fb_info->modelist);
1470
1471 pm_runtime_enable(&host->pdev->dev);
1472
1473 ret = mxsfb_init_fbinfo(host);
1474 if (ret != 0)
1475 goto fb_pm_runtime_disable;
1476
1477 mxsfb_dispdrv_init(pdev, fb_info);
1478
1479 if (!host->dispdrv) {
1480 pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
1481 if (IS_ERR(pinctrl)) {
1482 ret = PTR_ERR(pinctrl);
1483 goto fb_pm_runtime_disable;
1484 }
1485 }
1486
1487 if (!host->enabled) {
1488 writel(0, host->base + LCDC_CTRL);
1489 mxsfb_set_par(fb_info);
1490 mxsfb_enable_controller(fb_info);
1491 pm_runtime_get_sync(&host->pdev->dev);
1492 }
1493
1494 ret = register_framebuffer(fb_info);
1495 if (ret != 0) {
1496 dev_err(&pdev->dev, "Failed to register framebuffer\n");
1497 goto fb_destroy;
1498 }
	......
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

第 1473 行,调用 mxsfb_init_fbinfo 函数初始化 fb_info,重点是 fb_info 的 var、fix、fbops,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_remove、mxsfb_shutdown 等,

这里我们就简单的介绍了一下 mxsfb_probe 函数,至于其他的函数大家自行查阅

二、硬件原理图分析

三、LCD驱动程序编写

前面已经说了,6ULL 的 eLCDIF 接口驱动程序 NXP 已经编写好了,因此 LCD 驱动部分我们不需要去修改。我们需要做的就是按照所使用的 LCD 来修改设备树。
重点要注意三个地方:

①、LCD 所使用的 IO 配置。
②、LCD 屏幕节点修改,修改相应的属性值,换成我们所使用的 LCD 屏幕参数。
③、LCD 背光节点信息修改,要根据实际所使用的背光 IO 来修改相应的设备节点信息。

接下来我们依次来看一下上面这两个节点改如何去修改:

1.LCD 屏幕 IO 配置

首先要检查一下设备树中 LCD 所使用的 IO 配置,这个其实 NXP 都已经给我们写好了,不需要修改,不过我们还是要看一下。打开 imx6ull-luatao-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_ctrl,RGB LCD 的 4 根控制线配置项,包括 CLK、ENABLE、VSYNC 和 HSYNC。
第 37~40 行,子节点 pinctrl_pwm1,LCD 背光 PWM 引脚配置项。这个引脚要根据实际情况设置,这里我们建议大家在以后的学习或工作中,LCD 的背光 IO 尽量和半导体厂商的官方开发板一致。

注意示例代码中默认将 LCD 的电气属性都设置为 0X79,这里将其都改为 0X49,也就是将 LCD 相关 IO 的驱动能力改为 R0/1,也就是降低 LCD 相关 IO 的驱动能力。因为前面已经说了,正点原子的 ALPHA 开发板上的 LCD 接口用了三个 SGM3157 模拟开关,为了防止模拟开关影响到网络,因此这里需要降低 LCD 数据线的驱动能力,如果你所使用的板子没有用到模拟开关那么就不需要将 0X79 改为 0X49。

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

继续在 imx6ull-luatao-emmc.dts 文件中找到 lcdif 节点,节点内容如下所示:

1 &lcdif {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_lcdif_dat /* 使用到的 IO */
4 &pinctrl_lcdif_ctrl
5 &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_dat、pinctrl_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 模式,因此一个像素点占用 24bit,bits-per-pixel 属性要改为 24。
第 11 行的 bus-width 属性用于设置数据线宽度,因为要配置为 RGB888 模式,因此 bus-width 也要设置为 24。
第 13~30 行,这几行非常重要!因为这几行设置了 LCD 的时序参数信息,NXP 官方的 EVK开发板使用了一个 4.3 寸的 480*272 屏幕,因此这里默认是按照 NXP 官方的那个屏幕参数设置的。每一个属性的含义后面的注释已经写的很详细了,大家自己去看就行了,这些时序参数就是我们重点要修改的,需要根据自己所使用的屏幕去修改。

这里以正点原子的 (7 寸 800*480)屏幕为例,将 imx6ull-luatao-emmc.dts 文件中的 lcdif 节点改为如下内容:

&lcdif {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_lcdif_dat
		     &pinctrl_lcdif_ctrl;
	display = <&display0>;
	status = "okay";

	display0: display {
		bits-per-pixel = <24>;
		bus-width = <24>;

		display-timings {
			native-mode = <&timing0>;
			timing0: timing0 {
			clock-frequency = <30030>;
			hactive = <800>;
			vactive = <480>;
			hfront-porch = <210>;
			hback-porch = <46>;
			hsync-len = <1>;
			vback-porch = <23>;
			vfront-porch = <22>;
			vsync-len = <1>;

			hsync-active = <0>;
			vsync-active = <0>;
			de-active = <1>;
			pixelclk-active = <0>;
			};
		};
	};
};

3.LCD 屏幕背光节点信息

正点原子的 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-luatao-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-luatao-emmc.dts 文件中修改。

在整个 Linux 源码文件中搜索 compatible 属性的这两个值即可找到 imx6ull 的 pwm 驱动文件,imx6ull 的 PWM 驱动文件为 drivers/pwm/pwm-imx.c,这里我们就不详细的去分析这个文件了。继续在 imx6ull-luatao-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 频率设置为 5KHz(NXP 官方推荐设置)。
④、brightness-levels 属性描述亮度级别,范围为 0~255,0 表示 PWM 占空比为 0%,也就是亮度最低,255 表示 100%占空比,也就是亮度最高。至于设置几级亮度,大家可以自行填写此属性。
⑤、default-brightness-level 属性为默认亮度级别。

根据上述 5 点设置 backlight 节点,这个 NXP 已经给我们设置好了,大家在 imx6ull-luatao-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 行,设置背光使用 pwm1,PWM 频率为 5KHz。
第 4 行,设置背 8 级背光(0~7),分别为 0、4、8、16、32、64、128、255,对应占空比为0%、1.57%、3.13%、6.27%、12.55%、25.1%、50.19%、100%,如果嫌少的话可以自行添加一些其他的背光等级值。
第 5 行,设置默认背光等级为 6,也就是 50.19%的亮度。

关于背光的设备树节点信息就讲到这里,整个的 LCD 设备树节点内容我们就讲完了,按照这些节点内容配置自己的开发板即可。

四、运行测试

1.编译新的设备树

上一小节我们已经配置好了设备树,所以需要输入如下命令重新编译一下设备树:make dtbs等待编译生成新的 imx6ull-luatao-emmc.dtb 设备树文件,一会要使用新的设备树启动Linux 内核。

2.使能Linux logo显示

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

-> Device Drivers
-> Graphics support
-> Bootup logo (LOGO [=y])
-> Standard black and white Linux logo
-> Standard 16-color Linux logo
-> Standard 224-color Linux logo
在这里插入图片描述
这三个选项分别对应黑白、16 位、24 位色彩格式的 logo,我们把这三个都选中,都编译进 Linux 内核里面。设置好以后保存退出,重新编译 Linux 内核,编译完成以后使用新编译出来的 imx6ull-luatao-emmc.dtb 和 zImage 镜像启动系统,如果 LCD 驱动工作正常的话就会在 LCD 屏幕左上角出现一个彩色的小企鹅 logo,屏幕背景色为黑色,如图 所示:

在这里插入图片描述

3.设置 LCD 作为终端控制台

我们一直使用上位机作为Linux开发板终端,开发板通过串口和上位机进行通信。现在我们已经驱动起来 LCD 了,所以可以设置 LCD 作为终端,也就是开发板使用自己的显示设备作为自己的终端,然后接上键盘就可以直接在开发板上敲命令了,将 LCD 设置为终端控制台的方法如下:

1.设置 uboot 中的 bootargs

重启开发板,进入 Linux 命令行,重新设置 bootargs 参数的 console 内容,命令如下所示:

setenv bootargs ‘console=tty1 console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.5:/home/luatao/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.50:192.168.1.5:192.168.1.1:255.255.255.0::eth0:off’

这里我们设置了两遍 console,第一次设置 console=tty1,
也就是设置 LCD 屏幕为控制台,第二遍又设置console=ttymxc0,115200,也就是设置串口也作为控制台。相当于我们打开了两个 console,一个是 LCD,一个是串口,大家重启开发板就会发现 LCD 和串口都会显示 Linux 启动 log 信息。但是此时我们还不能使用 LCD 作为终端进行交互,因为我们的设置还未完成。

2.修改/etc/inittab 文件

打开开发板根文件系统中的/etc/inittab 文件,在里面加入下面这一行:

tty1::askfirst:-/bin/sh

添加完成以后的/etc/inittab 文件内容如图所示:
在这里插入图片描述
修改完成以后保存/etc/inittab 并退出,然后重启开发板,重启以后开发板 LCD 屏幕最后一行会显示下面一行语句:

Please press Enter to activate this console.

上述提示语句说的是:按下回车键使能当前终端,我们在前面实验已经将 I.MX6U-ALPHA 开发板上的 KEY 按键注册为了回车键,因此按下开发板上的 KEY 按键即可使能 LCD这个终端。当然了,大家也可以接上一个 USB 键盘,Linux 内核默认已经使能了 USB 键盘驱动了,因此可以直接使用 USB 键盘。

至此,我们就拥有了两套终端,一个是基于串口的上位机,一个就是我们开发板的 LCD屏幕,但是为了方便调试,我们以后还是以上位机为主。我们可以通过下面这一行命令向LCD 屏幕输出“hello linux!”

echo hello linux > /dev/tty1

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.LCD 自动关闭解决办法

默认情况下 10 分钟以后 LCD 就会熄屏,这个并不是代码有问题,而是 Linux 内核设置的,就和我们用手机或者电脑一样,一段时间不操作的话屏幕就会熄灭,以节省电能。解决这个问题有多种方法,我们依次来看一下:

1.按键盘唤醒

最简单的就是按下回车键唤醒屏幕,我们在前面将 I.MX6U-ALPHA 开发板上的 KEY按键注册为了回车键,因此按下开发板上的 KEY 按键即可唤醒屏幕。如果开发板上没有按键的话可以外接 USB 键盘,然后按下 USB 键盘上的回车键唤醒屏幕。

2.关闭 10 分钟熄屏功能

在 Linux 源码中找到 drivers/tty/vt/vt.c 这个文件,在此文件中找到 blankinterval 变量,如下所示:

179 static int vesa_blank_mode;
180 static int vesa_off_interval;
181 static int blankinterval = 10*60;

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.c 这个文件:

arm-linux-gnueabihf-gcc lcd_always_on.c -o lcd_always_on

编译生成 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 文件,然后重启开发板即可。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux LCD驱动实验 的相关文章

  • xsel -o 对于 OS X 等效项

    是否有一个等效的解决方案可以在 OS X 中抓取选定的文本 就像适用于 Linux 的 xsel o 一样 只需要当前的选择 这样我就可以在 shell 脚本中使用文本 干杯 埃里克 你也许可以安装xsel在 MacOS 上 更新 根据 A
  • 何时使用 pthread 条件变量?

    线程问题 看来 只有在其他线程调用 pthread cond notify 之前调用 pthread cond wait 时 条件变量才起作用 如果在等待之前发生通知 那么等待将被卡住 我的问题是 什么时候应该使用条件变量 调度程序可以抢占
  • Unix 命令列出包含字符串但*不*包含另一个字符串的文件

    如何递归查看包含一个字符串且不包含另一个字符串的文件列表 另外 我的意思是评估文件的文本 而不是文件名 结论 根据评论 我最终使用了 find name html exec grep lR base maps xargs grep L ba
  • Elasticsearch 无法写入日志文件

    我想激活 elasticsearch 的日志 当我运行 elasticsearch 二进制文件时 我意识到我在日志记录方面遇到问题 无法加载配置 这是输出 sudo usr share elasticsearch bin elasticse
  • 是否可以在Linux上将C转换为asm而不链接libc?

    测试平台为Linux 32位 但也欢迎 Windows 32 位上的某些解决方案 这是一个c代码片段 int a 0 printf d n a 如果我使用 gcc 生成汇编代码 gcc S test c 然后我会得到 movl 0 28 e
  • Bash 解析和 shell 扩展

    我对 bash 解析输入和执行扩展的方式感到困惑 对于输入来说 hello world 作为 bash 中的参数传递给显示其输入内容的脚本 我不太确定 Bash 如何解析它 Example var hello world displaywh
  • 无法加载 JavaHL 库。- linux/eclipse

    在尝试安装 Subversion 插件时 当 Eclipse 启动时出现此错误 Failed to load JavaHL Library These are the errors that were encountered no libs
  • Linux 中的无缓冲 I/O

    我正在写入大量的数据 这些数据数周内都不会再次读取 由于我的程序运行 机器上的可用内存量 显示为 空闲 或 顶部 很快下降 我的内存量应用程序使用量不会增加 其他进程使用的内存量也不会增加 这让我相信内存正在被文件系统缓存消耗 因为我不打算
  • 仅打印“docker-container ls -la”输出中的“Names”列

    发出时docker container ls la命令 输出如下所示 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a67f0c2b1769 busybox tail f dev
  • 如何查明CONFIG_FANOTIFY_ACCESS_PERMISSIONS是否启用?

    我想利用fanotify 7 http man7 org linux man pages man7 fanotify 7 html我遇到的问题是在某些内核上CONFIG FANOTIFY ACCESS PERMISSIONS不起作用 虽然C
  • 无法从 jenkins 作为后台进程运行 nohup 命令

    更新 根据下面的讨论 我编辑了我的答案以获得更准确的描述 我正在尝试从詹金斯运行 nohup 命令 完整的命令是 nohup java jar home jar server process 0 35 jar prod gt gt var
  • chown:不允许操作

    我有问题 我需要通过 php 脚本为系统中的不同用户设置文件所有者权限 所以我通过以下命令执行此操作 其中 1002 是系统的用户 ID file put contents filename content system chown 100
  • 在哪里可以找到并安装 pygame 的依赖项?

    我对 Linux 比较陌生 正在尝试安装 python 的 pygame 开发环境 当我运行 setup py 时 它说我需要安装以下依赖项 我找到并安装了其中之一 SDL 然而 其他人则更加难以捉摸 Hunting dependencie
  • 域套接字“sendto”遇到“errno 111,连接被拒绝”

    我正在使用域套接字从另一个进程获取值 就像 A 从 B 获取值一样 它可以运行几个月 但最近 A 向 B 发送消息时偶尔会失败 出现 errno 111 连接被拒绝 我检查了B域套接字绑定文件 它是存在的 我也在另一台机器上做了一些测试 效
  • Android 时钟滴答数 [赫兹]

    关于 proc pid stat 中应用程序的总 CPU 使用率 https stackoverflow com questions 16726779 total cpu usage of an application from proc
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • arm64和armhf有什么区别?

    Raspberry Pi Type 3 具有 64 位 CPU 但其架构不是arm64 but armhf 有什么区别arm64 and armhf armhf代表 arm hard float 是给定的名称Debian 端口 https
  • Pyaudio 安装错误 - “命令‘gcc’失败,退出状态 1”

    我正在运行 Ubuntu 11 04 Python 2 7 1 并想安装 Pyaudio 于是我跑了 sudo easy install pyaudio 在终端中 进程退出并显示以下错误消息 Searching for pyaudio Re
  • 如何在shell中输出返回码?

    我正在尝试通过调用自定义 shell 脚本sh bin sh c myscript sh gt log txt 2 gt 1 echo 该命令的输出是创建的后台进程的 PID 我想指导 bin sh保存返回码myscript sh到某个文件
  • Linux 中什么处理 ping?

    我想覆盖 更改 linux 处理 ping icmp echo 请求数据包的方式 这意味着我想运行自己的服务器来回复传入的 icmp 回显请求或其他 数据包 但为了使其正常工作 我想我需要禁用 Linux 的默认 ping icmp 数据包

随机推荐

  • 晶振是如何起振的?

    01 皮尔斯晶体振荡器 目前工作中用得最多的就是皮尔斯晶体振荡器 也就是下面这个结构 CL1 CL2为匹配电容 Rext通常为串联的几百欧姆电阻 有时也不加 上面这个结构可能看着不是很熟悉 我们把它转换一下 变成下面这个就熟悉些 上图中把R
  • TestNG 开源自动化测试框架

    摘要 TestNG是一个开源自动化测试框架 TestNG表示下一代 TestNG是类似于JUnit 特别是JUnit 4 但它不是一个JUnit扩展 它的灵感来源于JUnit 它的目的是优于JUnit的 尤其是当测试集成的类 主要内容 Te
  • 如何在Java中将GIF图像转换为PNG等图像格式?试试Aspose

    通常 GIF图像用于描述动画 动画GIF是按特定顺序组合在一起的帧的集合 但是 在某些情况下 必须将GIF图像转换为其他光栅图像格式 对于这种情况 本文演示了如何使用Java将GIF图像转换为PNG JPEG BMP和TIFF格式 在Jav
  • 【upload-labs】————12、Pass-11

    闯关界面 前后端检测判断 查看源代码 这里采用了白名单判断 但是 img path直接拼接 所以可以先上传一个符合白名单检测的jpg文件 之后再burpsuite中使用 00截断保存路径即可 截断保存路径 浏览器中访问
  • C++项目中调用C#库

    最近有个功能在实现的时候要么要求 C 版本比较高 C 17 要么要求的 Qt 版本比较高 要么要求 windows 版本比较高 而且也没有比较好的第三方 C 库 无意间发现 C 早就实现了该功能 故准备使用 C 实现具体功能 在 C 项目中
  • 差帧法——学习

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 引言 思路 伪代码 代码 详解 函数详解 二值化 高斯滤波 引言 个人理解 差帧法 就是相邻两帧之间图象的差值 通过对差值进行高斯滤波或者腐蚀 膨胀操作减小图象噪声
  • Vue基础

    Vue 是什么 Vue 读音 vju 类似于 view 是一套用于构建用户界面的渐进式框架 vue 的核心库只关注视图层 不仅易于上手 还便于与第三方库或既有项目整合 使用Vue将helloworld 渲染到页面上 指令 本质就是自定义属性
  • 操作系统复习总结(五)

    10 18 14 30 15 00 第五章 主存管理 主存储器 主存 or 内存 中央处理器为可直接访问的存储器 计算机系统的一个关键性资源 理想中的存储器 更大更快 更便宜的非易失性存储器 存储管理的功能 方式 连续分配方式 一个进程分配
  • LINUX-LVM简单配置

    1 在编辑设置中为虚拟机添加另一块新硬盘 2 fdisk dev sdb gt n新建分区 gt p选择默认主分区 gt 回车 gt 回车 gt 回车 gt t为新建分区分配id gt 8e gt 回车 3 pvcreate dev sdb
  • 字符串有关习题

    0 请问下面代码有没有毛病 为什么 1 gt gt gt input I love FishC com 2 gt gt gt print input 3 I love FishC com 答 有问题 给变量命名要避免与内置函数名字冲突 1
  • 安装老版本flash - 解决”正尝试安装的adobe flash player不是最新版本“的办法

    安装Flash player时提示 正尝试安装的adobe flash player不是最新版本 解决的办法是在运行中输入regedit 在注册表中找到 HKEY LOCAL MACHINE SOFTWARE Macromedia Flas
  • c语言中关于时间的函数

    本文从介绍基础概念入手 探讨了在C C 中对日期和时间操作所用到的数据结构和函数 并对计时 时间的获取 时间的计算和显示格式等方面进行了阐述 本文还通过大量的实例向你展示了time h头文件中声明的各种函数和数据结构的详细使用方法 关键字
  • 抓蓝牙数据包_蓝牙协议分析工具Wireshark/Frontline/Ellisys的使用

    一 声明 本专栏文章我们会以连载的方式持续更新 本专栏计划更新内容如下 第一篇 蓝牙综合介绍 主要介绍蓝牙的一些概念 产生背景 发展轨迹 市面蓝牙介绍 以及蓝牙开发板介绍 第二篇 Transport层介绍 主要介绍蓝牙协议栈跟蓝牙芯片之前的
  • JAVA String.intern()方法

    感谢原作者的分享 转载只为方便知识点 原文地址 http www runoob com java java string intern html 莫洛的笔记 尽管在输出中调用intern方法并没有什么效果 但是实际上后台这个方法会做一系列的
  • Java面试准备——计算机网络

    计算机网络相关面试重点整理 本文学习自GitHub上的JavaGuide项目 感谢大佬的资源 此处为自我学习与整理 原项目链接 JavaGuide OSI和TCP IP各层的结构和功能 都有哪些协议 我们平时学习计算机网络使用五层结构 比较
  • openlayers 3 之 getPixelFromCoordinate 为空

    在地图容器发生变化后 再调用其方法进行定位 暂时 报错setPosition的错误 跟踪源代码 发现是map getPixelFromCoordinate为null值 查找资料 发现是map还没有渲染完成 所以报错 解决办法 1 添加pos
  • Istio Pilot源码学习(三):xDS的异步分发

    本文基于Istio 1 18 0版本进行源码学习 5 xDS的异步分发 DiscoveryService主要包含下述逻辑 启动GRPC Server并接收来自Envoy端的连接请求 接收Envoy端的xDS请求 从ConfigControl
  • C#数组 一维、二维以及交错数组 C#学习杂记(五)

    1 一维数组基本概念 拥有连续的内存空间 存储一组相同类型的数据 数组长度不可更改 数组下标从0开始 2 数组基本使用 int arr 声明 arr new int 4 1 2 3 4 赋值并且初始化 string str str new
  • STM32F103ZET6【标准库函数开发】------05.通用定时器TIM4四个通道输出PWM信号

    STM32有四个通用定时器 现在介绍TIM4输出4路PWM的方法 TIM4可以选择不用重映射或者重映射 一 没有重映射 下面展示主要的time c main c函数的代码 include timer h void TIM4 PWM Init
  • linux LCD驱动实验

    文章目录 一 linux下LCD驱动解析 1 Framebuffer设备 2 LCD驱动解析 二 硬件原理图分析 三 LCD驱动程序编写 1 LCD 屏幕 IO 配置 2 LCD 屏幕参数节点信息修改 3 LCD 屏幕背光节点信息 四 运行