
准备
- 阅读LCD芯片数据手册
- LCD显示模块规格书
- 会看原理图
- 视频配套代码:IMX6ULL\source\03_LCD
- 内核自带的IMX6ULL LCD驱动程序
- 驱动源码:Linux-4.9.88\drivers\video\fbdev\mxsfb.c
- 设备树:
- arch/arm/boot/dts/imx6ull.dtsi
- arch/arm/boot/dts/100ask_imx6ull-14×14.dts
- 芯片资料
- IMX6ULL\开发板配套资料\datasheet\Core_board\CPU\IMX6ULLRM.pdf
- 《Chapter 34 Enhanced LCD Interface (eLCDIF)》
- IMX6ULL\开发板配套资料\datasheet\Core_board\CPU\IMX6ULLRM.pdf
- IMX6ULL的LCD裸机程序
- IMX6ULL\source\03_LCD\05_参考的裸机源码\03_font_test
查看厂家已经支持的LCD驱动
1.内核路径:/drivers/video/fbdev ls *.o
2.内核路径:/Linux-4.9.88 make menuconfig
/Device Drivers/Graphics support/Frame buffer Devices
不同接口的LCD硬件
像素:
像素的颜色怎么表示:
用红绿蓝三颜色来表示,可以用24位数据来表示红绿蓝,也可以用16位等等格式,比如:
- bpp:bits per pixel,每个像素用多少位来表示
- 24bpp:实际上会用到32位,其中8位未使用,其余24位中分别用8位表示红(R)、绿(G)、蓝(B)
- 16bpp:有rbg565,rgb555
- rgb565:用5位表示红、6位表示绿、5位表示蓝
- rgb555:16位数据中用5位表示红、5位表示绿、5位表示蓝,浪费一位
怎么把颜色发给LCD:
假设每个像素的颜色用16位来表示,那么一个LCD的所有像素点假设有xres * y res个, 需要的内存为:xres * yres * 16 / 8,也就是要设置所有像素的颜色,需要这么大小的内存。 这块内存就被称为framebuffer:
- Framebuffer中每块数据对应一个像素
- 每块数据的大小可能是16位、32位,这跟LCD上像素的颜色格式有关
- 设置好LCD硬件后,只需要把颜色数据写入Framebuffer即可
统一的LCD硬件模型:
不同接口:
MCU常用的8080接口LCD模组;
LCD(Liquid Crystal Display)的8080接口是一种并行通信接口标准,它得名于早期微处理器Intel 8080。这种接口允许LCD控制器与微处理器之间进行直接的数据交换,而不需要额外的控制逻辑。8080接口通常用于较大型的LCD显示模块,尤其是那些需要快速数据传输和高分辨率的应用中。
8080接口的主要特点包括:
- 并行数据总线:通常使用8位或16位的并行数据总线来传输数据,这比串行接口快得多,因为一次可以传输多个比特。
- 独立的读写控制:有单独的读和写控制信号,允许微处理器读取LCD的状态或向LCD写入数据。
- 地址/数据复用:在一些设计中,地址和数据可能共享相同的总线,通过地址/数据复用(A/D)信号来区分。
- 控制信号:除了数据总线外,还需要一些控制信号线,如片选(CS)、读/写(R/W)、使能(E)等,以协调数据的传输。
- 时序要求:8080接口有特定的时序要求,包括设置时间和保持时间,确保数据在传输过程中被正确捕获。
由于其较高的数据传输速率,8080接口在过去非常流行,但在现代设备中,由于成本、功耗和引脚数量的考虑,更倾向于使用SPI、I2C等串行接口。然而,在一些高性能或专业应用中,8080接口仍然被使用。
MPU常用的TFT RGB接口;
TFT(Thin Film Transistor)LCD屏幕的RGB接口是一种直接传输红绿蓝三基色信号的并行接口。这种接口用于将图像数据从主控芯片(通常是微控制器或图形处理器)传输到TFT LCD面板,以显示图像。RGB接口可以提供高质量的图像,因为它直接传输颜色信息,而不是经过压缩或转换。
RGB接口的关键组成部分包括:
1.数据线:
这些线分别传输红色、绿色和蓝色的信号。数据线的数量取决于接口的位深,例如6-bit、16-bit、18-bit或24-bit。例如,一个24-bit的RGB接口会有8条线分别对应红、绿、蓝三种颜色的8-bit数据。
2.同步信号:
- 垂直同步信号(VSYNC):表示一行扫描完成,新的一行开始。
- 水平同步信号(HSYNC):表示一帧扫描完成,新的一帧开始。
- 数据使能信号(DE):指示何时数据线上的数据是有效的。
3.控制信号:
- 片选信号(CS):选择特定的LCD模块或芯片。
- 数据/命令选择(DC):指示接下来的数据是数据还是命令。
4.电源线:
包括VCC(正电压)和GND(地),以及可能的LED背光控制线。
TFT RGB接口的时序非常重要,必须严格遵守,以确保图像数据正确地显示在屏幕上。时序包括数据的有效期、同步信号的宽度和间隔等。
使用RGB接口的TFT LCD屏幕常见于需要高分辨率和色彩准确性的应用,如工业控制面板、高端消费电子产品和专业监视器。然而,RGB接口的使用也意味着更高的成本和复杂性,因为需要较多的引脚和信号线,这在空间受限或成本敏感的设计中可能不是最佳选择。因此,对于一些应用,更简单的接口如SPI、I2C或串行RGB(如LVDS)可能会被选用。
MIPI标准:
- MIPI-DSI、MIPI-CSI、LVDS等接口解析:https://blog.csdn.net/u014470361/article/details/88891255
MIPI表示Mobile Industry Processor Interface,即移动产业处理器接口。是MIPI联盟发起的为移动应用处理器制定的开放标准和一个规范。主要是手机内部的接口(摄像头、显示屏接口、射频/基带接口)等标准化,从而减少手机内部接口的复杂程度及增加设计的灵活性。
对于LCD,MIPI接口可以分为3类:
- MIPI-DBI (Display Bus Interface)
- 既然是Bus(总线),就是既能发送数据,也能发送命令,常用的8080接口就属于DBI接口。
- Type B (i-80 system), 8-/9-/16-/18-/24-bit bus
- Type C (Serial data transfer interface, 3/4-line SPI)
- MIPI-DPI (Display Pixel Interface)
- Pixel(像素),强调的是操作单个像素,在MPU上的LCD控制器就是这种接口
- Supports 24 bit/pixel (R: 8-bit, G: 8-bit, B: 8-bit)
- Supports 18 bit/pixel (R: 6-bit, G: 6-bit, B: 6-bit)
- Supports 16 bit/pixel (R: 5-bit, G: 6-bit, B: 5-bit)
- MIPI-DSI (Display Serial Interface)
- Serial,相比于DBI、DPI需要使用很多接口线,DSI需要的接口线大为减少
- Supports one data lane/maximum speed 500Mbps
- Supports DSI version 1.01
- Supports D-PHY version 1.00
Frambuffer驱动程序框架
介绍:
Linux的帧缓冲(framebuffer)驱动程序框架是一个用于支持各种显示设备的标准接口。它将显存抽象成一种设备,允许用户空间的应用程序直接访问显示缓冲区进行读写操作,从而实现图形输出。
分为上下两层:
- fbmem.c:承上启下
- 实现、注册file_operations结构体
- 把APP的调用向下转发到具体的硬件驱动程序
- xxx_fb.c:硬件相关的驱动程序
- 实现、注册fb_info结构体
- 实现硬件操作
驱动核心:
分配 struct fb_info:
- framebuffer_alloc
- 这是帧缓冲的核心数据结构,包含了设备的各种信息,如屏幕大小、颜色深度、缓冲区地址等。
设置fb_info:
- var
- struct fb_var_screeninfo: 包含了可变的屏幕信息,如分辨率、位深度等。
- struct fb_fix_screeninfo: 包含了固定不变的屏幕信息,如物理尺寸、视频内存地址等。
- fbops:
- 这是file_operations结构体的一个子集,专门用于处理帧缓冲设备的特定操作,如fb_read()、fb_write()等。
- 硬件相关操作
注册fb_info
register_framebuffer
不同接口时序分析
8080接口LCD时序分析
以LCD_3.5寸_320x480_ILI9488液晶显示模块为例
8080接口原理图:
8080接口:
时序图:ILI9488驱动芯片数据手册第325页:
时序最小时间要求:
步骤:因为STM32系列中的高端型号一般含有FSMC(Flexible Static Memory Controller,灵活静态存储器控制器);
TFT-RGB接口LCD时序分析
LCD数据手册参考:7.0-13SPEC(7寸1024600TN-RGB)
水平和垂直时序图:
时间延迟要求:
电子枪模型:
- 每来一个DCLK,电子枪向右移动一点;
- 到达1这个位置后,通过水平同步脉冲信号HSD控制电子枪到达2这个位置;
- 当电子枪移动到3这个位置后,通过垂直同步脉冲信号VSD控制控制电子枪回到原点位置;
IMX6ULL的LCD控制器
介绍:
LCD控制器是专门设计用于控制和驱动LCD(Liquid Crystal Display,液晶显示器)显示内容的集成电路或子系统。它在嵌入式系统中扮演着类似计算机显卡的角色,负责将存储在显存中的图像数据转换成适当的信号,然后发送给LCD面板,以实现图像的显示。
LCD控制器的主要功能包括:
- 数据传输:从系统的内存中读取图像数据,转换成适合LCD面板的信号格式。
- 控制信号生成:生成必要的控制信号,如时序信号、刷新率信号等,确保LCD面板正确地显示图像。
- 显示参数配置:支持不同的显示模式和分辨率,可以根据不同的LCD屏幕规格进行配置,比如像素数量、灰度级别、色彩深度等。
- 图形处理:可能包含一些基本的图形处理能力,如图像缩放、旋转、翻转和颜色空间转换等。
- 接口连接:提供与主机处理器或其他组件的接口,如8080接口、SPI、I2C、RGB或TFT-LCD接口等。
- 省电模式:在不显示图像时,可以进入低功耗模式以节省电力。
不同类型和规模的LCD控制器可以支持不同的LCD类型,包括段式LCD、字符型LCD、图形LCD和TFT-LCD等。例如,有些控制器专门设计用于驱动TFT-LCD,它们通常具备更高级的特性,如高分辨率支持和更快的刷新率。
在嵌入式系统中,LCD控制器可以是独立的芯片,也可以集成在微控制器、微处理器或SoC(System on Chip,系统级芯片)中。例如,S3C44B0X和S3C2440就是包含LCD控制器的SoC,可以支持不同分辨率和色彩深度的LCD屏幕。
IMX6ULL的LCD控制器:
参考资料:IMX6ULL\开发板配套资料\datasheet\Core_board\CPU\IMX6ULLRM.pdf
- 《Chapter 34 Enhanced LCD Interface (eLCDIF)》
硬件框图:
IMX6ULL的LCD控制器名称为eLCDIF(Enhanced LCD Interface,增强型LCD接口),主要特性如下:
- 支持MPU模式:有些显示屏自带显存,只需要把命令、数据发送给显示屏即可;就是前面讲的8080接口
- VSYNC模式:跟MPU模式类似,多了VSYNC信号。针对高速数据传输(行场信号)
- 支持DOTCLK模式:RGB接口,就是前面讲的TFT-RGB接口
- 支持ITU-R BT.656接口,可以把4:2:2 YcbCr格式的数据转换为模拟电视信号
- 8/16/18/24/32 bit 的bpp数据都支持,取决于IO的复用设置及寄存器配置
- MPU模式,VSYNC模式,DOTCLK模式,都可以配置时序参数。
上图是IMX6ULL的LCD控制器框图。 我们在内存中划出一块内存,称之为显存,软件把数据写入显存。 设置好LCD控制器之后,它会通过AXI总线协议从显存把RGB数据读入FIFO,再到达LCD接口(LCD Interface)。 LCD控制器有两个时钟域:外设总线时钟域,LCD像素时钟域。前者是用来让LCD控制器正常工作,后者是用来控制电子枪移动。 上图的Read_Data操作,在MPU模式下才用到;我们采用的是DCLK模式,因此不予考虑。更详细的内容可以查看IMX6ull芯片手册《Chapter 34 Enhanced LCD Interface (eLCDIF)》。
数据传输与处理:
P213
LCD控制器寄存器简介:
查看任何芯片的LCD控制器寄存器时,记住几个要点:
① 怎么把LCD的信息告诉LCD控制器:即分辨率、行列时序、像素时钟等; ② 怎么把显存地址、像素格式告诉LCD控制器。
上图是我们将要使用到的寄存器,点击上图右边Section/page自动跳转到对应寄存器详细信息;
P126,34.6 eLCDIF Memory Map/Register Definition-eLCDIF内存映射/寄存器定义
LCD驱动程序分析-IMX6ULL
mxsfb.c
LCD驱动程序框架
驱动程序框架+硬件操作
- 分配 fb_info
- 设置 fb_info
- 注册 fb_info
- 硬件相关的设置(参考裸机代码):
- 引脚设置
- 时钟设置
- LCD控制器设置
配置引脚
查看LCD原理图确定需要使用的所有引脚;
使用pinctrl子系统配置引脚(有专门工具生成);
放入设备树
根节点下添加子节点信息:
配置时钟
阅读芯片手册找到需要配置的Clocks;
给设备树子节点添加时钟信息;
参考:arch/arm/boot/dts/imx6ull.dtsi
定义了3个时钟:
- pix:Pixel clock,用于LCD接口,设置为LCD手册上的参数
- axi:AXI clock,用于传输数据、读写寄存器,使能即可
- disp_axi:一个虚拟的时钟,可以不用设置
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";
};
驱动获得时钟:
host->clk_pix = devm_clk_get(&host->pdev->dev, "pix");
if (IS_ERR(host->clk_pix)) {
host->clk_pix = NULL;
ret = PTR_ERR(host->clk_pix);
goto fb_release;
}
host->clk_axi = devm_clk_get(&host->pdev->dev, "axi");
if (IS_ERR(host->clk_axi)) {
host->clk_axi = NULL;
ret = PTR_ERR(host->clk_axi);
dev_err(&pdev->dev, "Failed to get axi clock: %d\n", ret);
goto fb_release;
}
host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi");
if (IS_ERR(host->clk_disp_axi)) {
host->clk_disp_axi = NULL;
ret = PTR_ERR(host->clk_disp_axi);
dev_err(&pdev->dev, "Failed to get disp_axi clock: %d\n", ret);
goto fb_release;
}
设置频率:只需要设置pixel clock的频率
ret = clk_set_rate(host->clk_pix,
PICOS2KHZ(fb_info->var.pixclock) * 1000U);
使能时钟
clk_enable_pix(host);
clk_prepare_enable(host->clk_pix);
clk_enable_axi(host);
clk_prepare_enable(host->clk_axi);
clk_enable_disp_axi(host);
clk_prepare_enable(host->clk_disp_axi);
配置LCD控制器之获得LCD参数
在设备树里指定LCD参数
framebuffer-mylcd {
compatible = "100ask,lcd_drv";
pinctrl-names = "default";
pinctrl-0 = <&mylcd_pinctrl>;
backlight-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
<&clks IMX6UL_CLK_LCDIF_APB>;
clock-names = "pix", "axi";
display = <&display0>;
display0: display {
bits-per-pixel = <24>;
bus-width = <24>;
display-timings {
native-mode = <&timing0>;
timing0: timing0_1024x768 {
clock-frequency = <50000000>;
hactive = <1024>;
vactive = <600>;
hfront-porch = <160>;
hback-porch = <140>;
hsync-len = <20>;
vback-porch = <20>;
vfront-porch = <12>;
vsync-len = <3>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <0>;
};
};
};
};
从设备树获得参数
参考内核文件:
- drivers\video\of_display_timing.c
- drivers\video\fbdev\mxsfb.c
配置LCD控制器之寄存器操作
配合视频食用
单buffer和多buffer区别
源码
- 内核自带的LCD驱动程序
- IMX6ULL驱动源码:Linux-4.9.88\drivers\video\fbdev\mxsfb.c
- STM32MP157的驱动程序是基于GPU的,在Linux-5.4里没有mxsfb.c,可以参考另一个:
- Linux-5.4\drivers\video\fbdev\goldfishfb.c
- 在视频里基于IMX6ULL的mxsfb.c来讲解,我们把这个驱动程序也放到GIT仓库里
- IMX6ULL\source\03_LCD\12_lcd_drv_imx6ull_from_kernel_4.9.88
- STM32MP157\source\A7\03_LCD\12_lcd_drv_imx6ull_from_kernel_4.9.88
- 使用多buffer的APP参考程序,在GIT仓库中
- IMX6ULL\source\03_LCD\13_multi_framebuffer_example\testcamera
- STM32MP157\source\A7\03_LCD\13_multi_framebuffer_example\testcamera
单Buffer的缺点
- 如果APP速度很慢,可以看到它在LCD上缓慢绘制图案
- 即使APP速度很高,LCD控制器不断从Framebuffer中读取数据来显示,而APP不断把数据写入Framebuffer
- 假设APP想把LCD显示为整屏幕的蓝色、红色
- 很大几率出现这种情况:
- LCD控制器读取Framebuffer数据,读到一半时,在LCD上显示了半屏幕的蓝色
- 这是APP非常高效地把整个Framebuffer的数据都改为了红色
- LCD控制器继续读取数据,于是LCD上就会显示半屏幕蓝色、半屏幕红色
- 人眼就会感觉到屏幕闪烁、撕裂
使用多Buffer来改进
上述两个缺点的根源是一致的:Framebuffer中的数据还没准备好整帧数据,就被LCD控制器使用了。 使用双buffer甚至多buffer可以解决这个问题:
- 假设有2个Framebuffer:FB0、FB1
- LCD控制器正在读取FB0
- APP写FB1
- 写好FB1后,让LCD控制器切换到FB1
- APP写FB0
- 写好FB0后,让LCD控制器切换到FB0
流程如下:
驱动:分配多个buffer
fb_info->fix.smem_len = SZ_32M;
fbi->screen_base = dma_alloc_writecombine(fbi->device,
fbi->fix.smem_len,
(dma_addr_t *)&fbi->fix.smem_start,
GFP_DMA | GFP_KERNEL);
驱动:保存buffer信息
fb_info->fix.smem_len // 含有总buffer大小
fb_info->var // 含有单个buffer信息
APP:读取buffer信息
ioctl(fd_fb, FBIOGET_FSCREENINFO, &fix);
ioctl(fd_fb, FBIOGET_VSCREENINFO, &var);
// 计算是否支持多buffer,有多少个buffer
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
nBuffers = fix.smem_len / screen_size;
APP:使能多buffer
var.yres_virtual = nBuffers * var.yres;
ioctl(fd_fb, FBIOPUT_VSCREENINFO, &var);
APP:写buffer
fb_base = (unsigned char *)mmap(NULL , fix.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
/* get buffer */
pNextBuffer = fb_base + nNextBuffer * screen_size;
/* set buffer */
lcd_draw_screen(pNextBuffer, colors[i]);
APP:开始切换buffer
/* switch buffer */
var.yoffset = nNextBuffer * var.yres;
ioctl(fd_fb, FBIOPAN_DISPLAY, &var);
驱动:切换buffer
// fbmem.c
fb_ioctl
do_fb_ioctl
fb_pan_display(info, &var);
err = info->fbops->fb_pan_display(var, info) // 调用硬件相关的函数
APP:等待切换完成(在驱动程序中已经等待切换完成了,所以这个调用并无必要)
ret = 0;
ioctl(fd_fb, FBIO_WAITFORVSYNC, &ret);
评论