抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

LCD 是很常用的一个外设,在搭配 QT 这样的 GUI 库下可以制作出非常精美的 UI 界面。

1.Linux下LCD驱动简析

1.1Framebuffer 设备

裸机 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。

file_operations 操作集 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write,
.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap,
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
.llseek = default_llseek,
};

通过freanmbuffer机制将底层的LCD抽象为/dev/fbx,x=0,1,应用程序可以通过操作/dev/fbx来操作屏幕。

Linux 下Framebuffer 驱动的编写流程:

Linux 内核将所有的 Framebuffer 抽象为一个叫做 fb_info 的结构体, fb_info 结构体包含了 Framebuffer 设备的完整属性和操作集合,因此每一个 Framebuffer 设备都必须有一个 fb_info。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* 互斥锁 */
struct mutex mm_lock; /* 互斥锁,用于 fb_mmap 和 smem_*域*/
struct fb_var_screeninfo var; /* 当前可变参数 */
struct fb_fix_screeninfo fix; /* 当前固定参数 */
struct fb_monspecs monspecs; /* 当前显示器特性 */
struct work_struct queue; /* 帧缓冲事件队列 */
struct fb_pixmap pixmap; /* 图像硬件映射 */
struct fb_pixmap sprite; /* 光标硬件映射 */
struct fb_cmap cmap; /* 当前调色板 */
struct list_head modelist; /* 当前模式列表 */
struct fb_videomode *mode; /* 当前视频模式 */

#ifdef CONFIG_FB_BACKLIGHT /* 如果 LCD 支持背光的话 */
/* assigned backlight device */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev; /* 背光设备 */

/* Backlight level curve */
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
.........
struct fb_ops *fbops;/* 帧缓冲操作函数集 */
struct device *device; /* 父设备 */
struct device *dev; /* 当前 fb 设备 */
int class_flag; /* 私有 sysfs 标志 */
.........
char __iomem *screen_base; /* 虚拟内存基地址(屏幕显存) */
unsigned long screen_size; /* 虚拟内存大小(屏幕显存大小) */
void *pseudo_palette; /* 伪 16 位调色板 */
..........
};

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函数原型如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int register_framebuffer(struct fb_info *fb_info)
    {
    int ret;

    mutex_lock(&registration_lock);
    ret = do_register_framebuffer(fb_info);
    mutex_unlock(&registration_lock);

    return ret;
    }
    • fb_info:需要上报的 fb_info。
    • 返回值: 0,成功;负值,失败。
  • 卸载用unregister_framebuffer

1.2LCD 驱动简析

LCD 裸机例程主要分两部分:

  • 获取 LCD 的屏幕参数。
  • 根据屏幕参数信息来初始化 eLCDIF 接口控制器。
  • 不同分辨率的 LCD 屏幕其 eLCDIF 控制器驱动代码都是一样的,只需要修改好对应的屏幕参数即可。
  • 屏幕参数信息属于屏幕设备信息内容,这些肯定是要放到设备树中的,因此我们本章实验的主要工作就是修改设备树。

打开 imx6ull.dtsi,然后找到 lcdif

1
2
3
4
5
6
7
8
9
10
lcdif: lcdif@021c8000 {
compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
reg = <0x021c8000 0x4000>;
interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>,
<&clks IMX6UL_CLK_DUMMY>;
clock-names = "pix", "axi", "disp_axi";
status = "disabled";
};

搜索compatible属性,即可找到对应的驱动函数:drivers/video/fbdev/mxsfb.c

1
2
3
4
5
static const struct of_device_id mxsfb_dt_ids[] = {
{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
{ /* sentinel */ }
};

驱动文件为mxsfb.c为platform驱动框架,驱动和设备匹配以后,mxsfb_probe函数执行:

  • struct mxsfb_info,给mxsfb_info申请内存,申请fb_info,然后将两个联系起来。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
    if (!host) {
    dev_err(&pdev->dev, "Failed to allocate IO resource\n");
    return -ENOMEM;
    }

    fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
    if (!fb_info) {
    dev_err(&pdev->dev, "Failed to allocate fbdev\n");
    devm_kfree(&pdev->dev, host);
    return -ENOMEM;
    }
    host->fb_info = fb_info;
    fb_info->par = host;
  • host->base就是内存映射以后的LCDIF外设的基地址。

    1
    2
    3
    4
    5
    6
    host->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(host->base)) {
    dev_err(&pdev->dev, "ioremap failed\n");
    ret = PTR_ERR(host->base);
    goto fb_release;
    }
  • mxsfb_init_fbinfo 初始化fb_info

    1
    ret = mxsfb_init_fbinfo(host);
  • register_framebuffer注册

    1
    2
    3
    4
    5
    ret = register_framebuffer(fb_info);
    if (ret != 0) {
    dev_err(&pdev->dev, "Failed to register framebuffer\n");
    goto fb_destroy;
    }

mxsfb_probe函数重点工作:

1.初始化fb_info并且向内核注册

2.初始化LCDIF控制器

mxsfb_init_fbinfo_dt函数会从设备树中读取相关属性信息。

mxsfb_init_fbinfo_dt:

1
host->id = of_alias_get_id(np, "lcdif");  //寻找节点

2.修改设备树参数

2.1屏幕引脚设置

屏幕引脚设置:

  • 将屏幕引脚电器属性0X49,就是修改LCD引脚驱动能力。

  • bus-width = <24>;打开绑定文档:

    1
    2
    3
    - bits-per-pixel : <16> for RGB565, <32> for RGB888/666.

    bits-per-pixel = <32>;
  • 修改过后 //我用的是480*800的屏幕。具体参数得看自己的屏幕尺寸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
&lcdif {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_lcdif_dat
&pinctrl_lcdif_ctrl>;
display = <&display0>;
status = "okay";

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

display-timings {
native-mode = <&timing0>;
timing0: timing0 {
clock-frequency = <31500000>;
hactive = <800>;
vactive = <480>;
hfront-porch = <40>;
hback-porch = <88>;
hsync-len = <48>;
vback-porch = <32>;
vfront-porch = <13>;
vsync-len = <3>;

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

image-20241209205731958

image-20241209205751137

2.2背光

一般屏幕背光用PWM控制亮度,一般测试屏幕的时候直接将背光引脚拉高或拉低。

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

1
/sys/devices/platform/backlight/backlight/backlight

brightness 表示当前亮度等级, max_bgigntness 表示最大亮度等级。

image-20241209214846948

当前屏幕亮度等级为 6,根据前面的分析可以,这个是 50%亮度。屏幕最大亮度等级为 7。如果我们要修改屏幕亮度,只需要向 brightness 写入需要设置的屏幕亮度等级即可。比如设置屏幕亮度等级为 7,那么可以使用如下命令:

1
echo 7 > brightness

3.实验

修改好设备树之后,输入命令,即可在屏幕上显示字符。

image-20241209210211309

1e7e9c47d341740723ca600f96d4568

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

4.1设置uboot中的bootargs

1
setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.10.100:/home/zwl/linux/nfs/my-rootfs ip=192.168.10.50:192.168.10.100:192.168.10.1:255.255.255.0::eth0:off' 

4.2修改/etc/inittab文件

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

1
tty1::askfirst:-/bin/sh  

插上键盘,就可输入了。

342978fec565b4955ea0560f479fe54

5.LCD 自动关闭解决方法

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

1、按键盘唤醒

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

2、关闭 10 分钟熄屏功能

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

1
2
3
static int vesa_blank_mode;
static int vesa_off_interval;
static int blankinterval = 10*60;

blankinterval 变量控制着 LCD 关闭时间,默认是 10*60,也就是 10 分钟。将 blankinterval的值改为 0 即可关闭 10 分钟熄屏的功能,修改完成以后需要重新编译 Linux 内核,得到新的zImage,然后用新的 zImage 启动开发板。

3、编写一个 APP 来关闭熄屏功能

在 ubuntu 中新建一个名为 lcd_always_on.c 的文件,然后在里面输入如下所示内容:

1
2
3
4
5
6
7
8
9
10
11
12
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>

int main(int argc, char *argv[])
{
int fd;
fd = open("/dev/tty1", O_RDWR);
write(fd, "\033[9;0]", 8);
close(fd);
return 0;
}

使用如下命令编译 lcd_always_on.c 这个文件:

1
ueabihf-gcc lcd_always_on.c -o lcd_always_on

编译生成 lcd_always_on 以后将此可执行文件拷贝到开发板根文件系统的/usr/bin 目录中,然后给予可执行权限。设置 lcd_always_on 这个软件为开机自启动,打开/etc/init.d/rcS,在此文件最后面加入如下内容:

1
2
3
cd /usr/bin
./lcd_always_on
cd ..

修改完成以后保存/etc/init.d/rcS 文件,然后重启开发板即可。

评论