电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
【i.MX6ULL】驱动开发11——LCD驱动实践
分 享
扫描二维码分享
【i.MX6ULL】驱动开发11——LCD驱动实践
i.MX6ULL
嵌入式
LCD
码农爱学习
关注
发布时间: 2022-01-05
丨
阅读: 505
之前在Linux系统移植时提到过LCD驱动,本篇来看下Linux设备树如何配置LCD驱动。 # 1 知识点 首先需要了解一个新的概念:Framebuffer ## 1.1 Framebuffer Framebuffer直译即帧缓冲,简称 fb,它是Linux将系统中所有跟显示有关的硬件以及软件集合起来,将底层的LCD虚拟抽象出一 个/dev/fbX设备,应用程序可以通过操作/dev/fbX来实现对屏幕的显示控制。 NXP官方Linux内核已默认开启了LCD驱动,在dev/目录下可以看到fb0这样一个设备 ![](https://cf02.ickimg.com/bbsimages/202112/5778d5b976c16eceef68785d743799a9.png) Framebuffer在内核中的表现就是fb_info结构体: ![](https://cf02.ickimg.com/bbsimages/202112/dafddb4850817a6c31f6761fc476a1bf.png) 完整的结构体定义如下: ```c struct fb_info { atomic_t count; int node; int flags; struct mutex lock; /@@* Lock for open/release/ioctl funcs */ struct mutex mm_lock; /@@* Lock for fb_mmap and smem_* fields */ struct fb_var_screeninfo var; /@@* 当前的可变参数 */ struct fb_fix_screeninfo fix; /@@* 当前的固定参数 */ struct fb_monspecs monspecs; /@@* Current Monitor specs */ struct work_struct queue; /@@* Framebuffer event queue */ struct fb_pixmap pixmap; /@@* Image hardware mapper */ struct fb_pixmap sprite; /@@* Cursor hardware mapper */ struct fb_cmap cmap; /@@* Current cmap */ struct list_head modelist; /@@* mode list */ struct fb_videomode *mode; /@@* current mode */ #ifdef CONFIG_FB_BACKLIGHT /@@* 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 #ifdef CONFIG_FB_DEFERRED_IO struct delayed_work deferred_work; struct fb_deferred_io *fbdefio; #endif struct fb_ops *fbops; /@@* 帧缓冲操作函数集 */ struct device *device; /@@* This is the parent */ struct device *dev; /@@* This is this fb device */ int class_flag; /@@* private sysfs flags */ #ifdef CONFIG_FB_TILEBLITTING struct fb_tile_ops *tileops; /@@* Tile Blitting */ #endif char __iomem *screen_base; /@@* 虚拟内存基地址(屏幕显存) */ unsigned long screen_size; /@@* 虚拟内存大小(屏幕显存大小) */ void *pseudo_palette; /@@* 伪16位调色板 */ #define FBINFO_STATE_RUNNING 0 #define FBINFO_STATE_SUSPENDED 1 u32 state; /@@* Hardware state i.e suspend */ void *fbcon_par; /@@* fbcon use-only private area */ /@@* From here on everything is device dependent */ void *par; /@@* we need the PCI or similar aperture base/size not smem_start/size as smem_start may just be an object allocated inside the aperture so may not actually overlap */ struct apertures_struct { unsigned int count; struct aperture { resource_size_t base; resource_size_t size; } ranges[0]; } *apertures; bool skip_vt_switch; /@@* no VT switch on suspend/resume required */ }; ``` 注意结构体中的**fb_fops**这一项,/dev/fb0 是个字符设备,fb_fops就是它的文件操作结构体,它的file_operations操作集在drivers/video/fbdev/core/fbmem.c 文件中: ![](https://cf02.ickimg.com/bbsimages/202112/006d52e2f1263054c0ff83b90d53ecf7.png) ```c 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, }; ``` 可以看到有熟悉的open、release等函数接口。 因此,**LCD驱动的重点就是初始化fb_info里面的各个成员**。 fb_info结构体的成员变量很多,需要重点关注的是这几个: - var:当前的可变参数 - fix:当前的固定参数 - fbops:帧缓冲操作函数集 - screen_base:虚拟内存基地址(屏幕显存) - screen_size:虚拟内存大小(屏幕显存大小) - pseudo_palette:伪16位调色板 初始化完成fb_info后,**通过register_framebuffer函数向内核注册**刚刚初始化的fb_info。 ## 1.2 LCD驱动文件mxsfb介绍 LCD的驱动文件为**mxsfb.c**,这是一种platform驱动框架,驱动和设备匹配之后,mxsfb_probe函数就会执行。 LCD的初始化通过**mxsfb_probe函数**来实现,该函数的主要功能有: - 申请fb_info - 初始化fb_info结构体中的各个成员变量 - 初始化eLCDIF控制器 - 使用register_framebuffer函数向Linux内核注册初始化好的fb_info 该函数位于:/drivers/video/fbdev/mxsfb.c中 ![](https://cf02.ickimg.com/bbsimages/202112/bb21e912ec280841f240c3f04792dfb8.png) 该函数的实现如下: ```c static int mxsfb_probe(struct platform_device *pdev) { const struct of_device_id *of_id = of_match_device(mxsfb_dt_ids, &pdev->dev); struct resource *res; struct mxsfb_info *host; //<-----NXP的fb_info struct fb_info *fb_info; //<-----Linux的fb_info struct pinctrl *pinctrl; int irq = platform_get_irq(pdev, 0); int gpio, ret; if (of_id) pdev->id_entry = of_id->data; gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0); if (gpio == -EPROBE_DEFER) return -EPROBE_DEFER; //省略... fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);//<--------申请fb_info if (!fb_info) { dev_err(&pdev->dev, "Failed to allocate fbdev\n"); devm_kfree(&pdev->dev, host); return -ENOMEM; } host->fb_info = fb_info; //<---将mxsfb_info与fb_info联系起来 fb_info->par = host; //省略... ret = mxsfb_init_fbinfo(host); if (ret != 0) goto fb_pm_runtime_disable; mxsfb_dispdrv_init(pdev, fb_info); //省略... ret = register_framebuffer(fb_info); //<------------注册 if (ret != 0) { dev_err(&pdev->dev, "Failed to register framebuffer\n"); goto fb_destroy; } console_lock(); ret = fb_blank(fb_info, FB_BLANK_UNBLANK); console_unlock(); if (ret < 0) { dev_err(&pdev->dev, "Failed to unblank framebuffer\n"); goto fb_unregister; } dev_info(&pdev->dev, "initialized\n"); } ``` 其中,register_framebuffer函数的原型如下: ![](https://cf02.ickimg.com/bbsimages/202112/db9991f46a0c7b9581f457aee45c26e3.png) 函数参数和返回值含义: - fb_info:需上报的fb_info - 返回值:0-成功,负值-失败 ## 1.3 LCD 驱动程序编写 6ULL的eLCDIF接口驱动程序 NXP 已经编 写好了,因此 LCD 驱动部分我们不需要去修改。我们需要做的就是按照所使用的 LCD 来修改设备树。 ### 1.3.1 查看设备树 1.3 先来看一下NXP官方编写的Linux下的 LCD 驱动。打开 imx6ull.dtsi,然后找到 lcdif节点内容: ![](https://cf02.ickimg.com/bbsimages/202112/cfe9e6bda4bfa1211249e4f4bf5929a9.png) ```c lcdif: lcdif@021c8000 { compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif"; reg = <0x021c8000 0x4000>; interrupts =
; clocks = <&clks IMX6UL_CLK_LCDIF_PIX>, <&clks IMX6UL_CLK_LCDIF_APB>, <&clks IMX6UL_CLK_DUMMY>; clock-names = "pix", "axi", "disp_axi"; status = "disabled"; }; ``` 其中021c8000 这个地址,可以从参考手册中找到对应的介绍: ![](https://cf02.ickimg.com/bbsimages/202112/e466aa4fca6c1e87e36dc0c1e371042a.png) ### 1.3.2 屏幕IO配置 打开 imx6ull-myboard.dts 文件,在 iomuxc 节点中找到如下内容: ![](https://cf02.ickimg.com/bbsimages/202112/7e606fea618e34f484798c01fab1b63a.png) 具体为: ```c pinctrl_lcdif_dat: lcdifdatgrp { fsl,pins = < MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79 MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79 MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x79 MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x79 MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x79 MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x79 MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x79 MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x79 MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x79 MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x79 MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x79 MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x79 MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x79 MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x79 MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x79 MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x79 MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x79 MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x79 MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x79 MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x79 MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x79 MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x79 MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x79 MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x79 >; }; pinctrl_lcdif_ctrl: lcdifctrlgrp { fsl,pins = < MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79 MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79 MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79 MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79 >; }; pinctrl_pwm1: pwm1grp { fsl,pins = < MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0 >; }; ``` 这里有3个节点: - 子节点**pinctrl_lcdif_dat **,为 RGB LCD 的 **24根数据线**配置项 - 子节点 **pinctrl_lcdif_ctrl **,为RGB LCD 的 **4根控制线**配置项,包括 CLK、ENABLE、VSYNC 和 HSYNC - 子节点 **pinctrl_pwm1 **,为RGB LCD 的背光亮度配置项 ### 1.3.3 屏幕参数配置 在imx6ull-myboard.dts 文件中找到**lcdif 节点**,根据自己使用的LCD,修改为对应的参数。 下面是NXP官方板子的参数: ![](https://cf02.ickimg.com/bbsimages/202112/d785ca7fc95282eec0870fbc80979719.png) 我用的野火7寸屏(GT911,800x480),其参数为: | 参数 | 值 | | ------ | ---- | | width | 800 | | height | 480 | | HBP | 46 | | HFP | 22 | | VBP | 23 | | VFP | 22 | | HSPW | 1 | | VSPW | 1 | 修改后的lcdif 节点如下: ```c &lcdif { pinctrl-names = "default"; /@@* 使用到的 IO */ pinctrl-0 = <&pinctrl_lcdif_dat &pinctrl_lcdif_ctrl &pinctrl_lcdif_reset>; display = <&display0>; status = "okay"; display0: display { /@@* LCD 属性信息 */ bits-per-pixel = <16>; /@@* 一个像素占用几个bit */ bus-width = <24>; /@@* 总线宽度 */ display-timings { native-mode = <&timing0>; /@@* 时序信息 */ timing0: timing0 { clock-frequency = <9200000>; /@@* LCD像素时钟,单位Hz */ hactive = <800>; /@@* LCD X轴像素个数 */ vactive = <480>; /@@* LCD Y轴像素个数 */ hfront-porch = <22>; /@@* LCD hfp 参数 */ hback-porch = <46>; /@@* LCD hbp 参数 */ hsync-len = <23>; /@@* LCD hspw 参数 */ vback-porch = <22>; /@@* LCD vbp 参数 */ vfront-porch = <4>; /@@* LCD vfp 参数 */ vsync-len = <1>; /@@* LCD vspw 参数 */ hsync-active = <0>; /@@* hsync 数据线极性 */ vsync-active = <0>; /@@* vsync 数据线极性 */ de-active = <1>; /@@* de 数据线极性 */ pixelclk-active = <0>; /@@* clk 数据线极性 */ }; }; }; }; ``` ### 1.3.4 屏幕背光配置 通过PWM信号来控制LCD屏幕背光的亮度 ```c pinctrl_pwm1: pwm1grp { fsl,pins = < MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0 >; }; ``` LCD 背光要用到PWM1,因此也要设置 PWM1 节点,在imx6ull.dtsi 文件中找到如下内容: ![](https://cf02.ickimg.com/bbsimages/202112/e689eec5f7f828b02a9c6f6dddf2573a.png) 这个节点信息不用修改,使用默认的配置即可。如果要修改的话,也不要修改这里,可以通过imx6ull-myboard.dts文件中进行修改。 imx6ull-myboard.dts中的pwm1节点: ```c &pwm1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_pwm1>; status = "okay"; }; ``` imx6ull-myboard.dts中的backlight节点: ```c backlight { compatible = "pwm-backlight"; pwms = <&pwm1 0 5000000>; brightness-levels = <0 4 8 16 32 64 128 255>; default-brightness-level = <6>; status = "okay"; }; ``` # 2 实验测试 ## 2.1使能Linux logo显示 uboot启动的时候,LCD左上角上会显示NXP的图标,而Linux内核启动的时候,LCD左上角上会显示一个小企鹅。因此,可以通过小企鹅logo的显示来验证LCD 驱动是否正常。 默认情况下是已经开启logo显示的,可以再确认一下。 在Linux内核源码目录,输入以下指令打开内核的图形化配置: ```sh make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig ``` Linux内核配置界面: ![](https://cf02.ickimg.com/bbsimages/202112/63b8b9112c7912e6d2d8a996cc839e05.png) 然后按下路径找到对应的配置项: ```sh -> Device Drivers -> Graphics support -> Bootup logo (LOGO [=y]) ``` 最终到达这个界面: ![](https://cf02.ickimg.com/bbsimages/202112/4976b2d4793843473fd3d48bf933ceae.png) 这三个选项分别对应黑白、16 位、24 位色彩格式的 logo。 ## 2.2 编译设备树 修改设备树中的lcdif节点后(主要是修改屏幕的参数),在Linux内核源码目录执行下面的命令,重新编译设备树并拷贝到网络启动位置。 ```sh make imx6ull-myboard.dtb cp arch/arm/boot/dts/imx6ull-myboard.dtb ~/myTest/tftpboot/nxp/ ``` 然后重启开发板,就可以在Linux内核驱动的时候看到屏幕上的企鹅图标了: ![](https://cf02.ickimg.com/bbsimages/202112/0b847355917fd8b9329f64c055e284dd.jpg) ## 2.3 设置LCD作为终端控制台 之前一直使用串口来显示板子的启动和调试信息,实际上可以设置 LCD 作为终端进行同步显示: ### 2.3.1 设置uboot的bootargs 重启开发板,在倒计时时按回充进入ubout,可以先看下之前的bootargs配置: ![](https://cf02.ickimg.com/bbsimages/202112/10c37d2ad12b662f32155e7dfd820e9e.png) 只需要在原来的基础上再添加`console=tty1`即可: ```sh setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.5.104:/home/xxpcb/myTest/nfs/rootfs,proto=tcp,nfsvers=4 rw ip=192.168.5.102:192.168.5.104:192.168.5.1:255.255.255.0::eth1:off' saveenv ``` 然后重启开发板,在Linux内核驱动的时候就可以在屏幕上看到输出信息了: ![](https://cf02.ickimg.com/bbsimages/202112/e979d41b05da6c2051e7cf2f31cc54bf.jpg) 对比一下串口输出的信息,可以看出屏幕输出到`Freeing unused kernel memory: 400K (8090e000 - 80972000)`这句后就没有了,没有出现**按下回车键继续**的提示,也没有显示开启自启动的hello word测试程序的打印,这是因为某些设置还未完成。 ![](https://cf02.ickimg.com/bbsimages/202112/cc797b841767496e7e762d2525af41f2.png) ### 2.3.2 修改/etc/inittab文件 该修改用于设置屏幕作为终端进行交互。 打开根文件系统中的/etc/inittab 文件,加入下面这一行: ```sh tty1::askfirst:-/bin/sh ``` ![](https://cf02.ickimg.com/bbsimages/202112/b8f7a77d2e333b1f4e64d04053897693.png) 保存后重启板子,并在板子的USB接口插上键盘,就可以通过键盘和板子交互了: ![](https://cf02.ickimg.com/bbsimages/202112/00c04e47d402a1526de7094fa71e063c.jpg) 现在通过板子插入键盘,也可以在屏幕上操作板子了。 注意,之前设置的开机启动的hello word程序的打印没有出现在屏幕上,是因为printf的输入没有设置的LCD中,我们可以通过将输出指向 /dev/tty1 来实现LCD屏幕的打印,比如测试屏幕输出hello linux: ```sh echo hello linux > /dev/tty1 ``` ![](https://cf02.ickimg.com/bbsimages/202112/1f3767a6b9cfb23a7cacedab40f9b670.jpg) ## 2.4 其它问题 ### 2.4.1 自动熄屏的问题 当没有操作LCD屏幕一段时间后,屏幕会自动黑屏,这时可以通过接入键盘按下**回车键**进行唤醒(也可以通过板子的**ON/OFF按键**进行唤醒,因为该按键也被赋予了回车键的功能)。 这个时间是在Linux源码的 drivers/tty/vt/vt.c中设置的,默认是10分钟(10*60秒)。 ![](https://cf02.ickimg.com/bbsimages/202112/b3512bdf428d06f92fd7f43d3c331144.png) 如果想让屏幕一直亮着,可以将改值设为0,并重新编辑Linux内核得到zImage,然后用新的zImage启动开发板。 如果不想修改zImage,另外一种方式可以创建一个开机启动的应用程序来控制屏幕不熄灭, `lcd_always_on.c`的内容为: ```c #include
#include
#include
int main(int argc, char *argv[]) { int fd; fd = open("/dev/tty1", O_RDWR); write(fd, "\033[9;0]", 8); close(fd); return 0; } ``` 在ubuntu中编译该程序,然后将可执行程序拷贝到板子的根文件系统中: ```sh arm-linux-gnueabihf-gcc lcd_always_on.c -o lcd_always_on cp lcd_always_on ~/myTest/nfs/rootfs/usr/bin/ ``` ![](https://cf02.ickimg.com/bbsimages/202112/3c80d12ebb0e34c39100073bb965d920.png) 然后,/etc/init.d/rcS中设置该程序开机自启动即可。 ![](https://cf02.ickimg.com/bbsimages/202112/49c1c2346f4c884aee55e1bd9e436be5.png) 保存后,重启开发板,屏幕就不会自动熄屏了。 ### 2.4.2 屏幕亮度调节 屏幕的亮度也是可以调节的,设备树中背光节点设置了8 个等级,可以在 0~7范围内进行亮度调节,进入下面的目录,可以查看当前屏幕的亮度: ```sh /sys/devices/platform/backlight/backlight/backlight ``` 通过下面的指令可以实时修改屏幕的亮度,比如修改亮度为1: ```sh echo 1 > brightness ``` ![](https://cf02.ickimg.com/bbsimages/202112/e8ddf681c0e3da7346b9cbb717f67fca.png) # 总结 本篇介绍了LCD屏幕驱动相关知识并进行了实验,因为NXP官方的板子和我这个板子的LCD引脚一样,因此主要的修改就是将设备树中的**lcdif 节点**的屏幕参数进行修改即可。 通过实验,可以将企鹅logo显示出来,并将板子的输出信息定向到了LCD屏幕显示,通过接入键盘可实现与Linux板子的交互。最后,还测试了屏幕熄屏和亮度调节功能。
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
码农爱学习
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交