LCD液晶屏驱动详解

您所在的位置:网站首页 液晶驱动原理有哪三种分类方式 LCD液晶屏驱动详解

LCD液晶屏驱动详解

2024-07-06 03:50| 来源: 网络整理| 查看: 265

开发环境: 开发板:JZ2440V3CPU:samsunS3C2440内核:Linux3.4.2编译工具:arm-linux-gcc 4.3.2LCD:4.3存液晶屏AT043TN24参考文献: LCD驱动程序详细讲解(一)_weixin_33935505的博客-CSDN博客LCD驱动详解 - Lilto - 博客园 (cnblogs.com)主题:s3c2440移植linux-3.4.2中的LCD驱动_大白菜的博客-CSDN博客【第2期】韦东山嵌入式Linux之第2期_驱动大全 (100ask.net) 1、LCD种类、电路连接及显示原理

LCD,即液晶显示器,是一种采用了液晶控制透光技术来实现色彩的显示器。LCD有很多种类型,比如STN、TFT、LTPS、OLED等。各有优缺点。JZ2440V3开发板上面配置的是TFT类型液晶显示器,也是目前最为主流的液晶显示器。

TFT-LCD的数据传输方式有2种:

单扫:对于一整屏的数据,从上到下,从左到右,一个一个地发送出来。

双扫:将一整屏的数据分为上下两部分,同时的从上到下,从左到右,一个一个的发送出来。

LCD的信号种类:

信号名称描述VSYNC垂直同步信号HSYNC水平同步信号VD[23:0]数据信号HCLK时钟信号LEND行结束信号PWREN电源开关信号 电路模块图 image-20210718154644618 LCD控制器原理图 image-20210718154827043 显示原理

我们除了配置一些寄存器告诉LCD控制器图像中像素的格式(RGB565),frameBuffer的首地址之类外,对于TFT LCD的访问还需要用到一些信号,所以需要通过配置寄存器来告诉LCD控制器这些信号的信息(比如何时发出控制信号,发出信号的持续时间等),举个例子: 向LCD驱动器发送图片数据时需要时钟控制(VCLK),一个时钟发送一个像素点,那么控制器就需要主动发出时钟信号,这个时钟是由哪个引脚发出的,发出的频率是多少,这个都是要配置寄存器的, 我们通过时序图来分析需要用到的一些信号以及如何去配置它们,如果是第一次了解LCD控制,直接看时序还是比较困难的,所以先给出一个形象的比喻 :

image-20210719222446353

frame buffer: 显存,用于存放LCD显示数据;frame buffer通过LCD控制器和LCD Panel建立一一映射关系;

LCD控制器: 参考LCD用户手册,配置LCD控制器,用于发出LCD控制信号,驱动LCD显示;

扫描方式: 如图所示,由start到end的扫描方向是:从左到右,从上到下(扫描方向的一种);

HSYNC: 行同步信号,用于行切换,一行扫描结束,需要扫描新行时,需要先发送行同步信号;

VSYNC: 列同步信号,用于列切换,一帧扫描结束,需要扫描新的一帧时,需要先发送列同步信号;

时钟信号: 每来一个时钟,扫描的点移位一;

上图中LD驱动器可以比喻成电子枪,LCD控制器就是控制这个电子枪的,它从显示缓存中拿像素数据传给电子枪并发送命令让电子枪发射像素颜色, 上图中,成像过程

LCD控制器发出VSYNC信号,告诉电子枪,要发出一张新帧了,然后电子枪把枪头调转到LCD屏幕的左上角准备开始发射像素发出VSYNC信号的同时,发出HSYNC信号(告诉电子枪新行开始, 从左向右动发射子弹吧)但是电子枪毕竟反应比较慢,过了少许开始发射子弹 对于上面两个过程,由于电子枪接受了VSYNC信号,调转枪头后,需要反应一段时间才能正常开始工作, 所以就白白扫射了几行的无效数据,相当于经过了几个HSYNC信号周期的时间, 一个HSYNC周期就是电子枪扫射一行的时间(从HSYNC信号开始扫射第一行直到到一行结束扫射结束所用时间),就出现了上方无效区当第一行结束时,LCD控制器又发出HSYNC信号, 电子枪枪头扭转到下一行新行开始发射数据, 但是枪头扭转的比较慢, 所以出现了左右的无效区(即第一行结束后,电子枪由于硬件原因要反应一段时间, 所以在右边出现了无效数据区, 调转枪头后, 也得反应一段时间开始发射子弹,所以出现了左边的无效区),有人会问电子枪如何知道第一行何时结束(其实是我们通过寄存器告诉LCD控制器第一行有多少个数据的,我们的屏幕分辨率是480*272, 即这个信息会设置到寄存器里), 当一行结束时,LCD控制器就不会再发有效像素数据,并且等待电子枪游离一段时间,之后再发下一行的HSYNC信号.loop第三个过程当扫描到最后一行结束时(一帧即将结束),LCD控制器就不会再发有效像素数据,并且等待电子枪游离一段时间,所以会继续往下扫描,出现了下方的无效区, 之后再发下一行的VSYNC信号, 之后回到过程1开始重复。

在工作中的显示器上,可以在四周看见黑色的边框。上方的黑框是因为当发出VSYNC信号时,需要经过若干行之后第一行数据才有效;下方的黑框是因为显示完所有行的数据时,显示器还没有扫描到最下边(VSYNC信号还没有发出),这时数据是无效的;左边的黑框是因为当发出HSYNC信号时,需要经过若干像素之后第一列数据才有效;右边的黑框是因为显示完一行数据时,显示器还没扫描到最右边(HSYNC信号还没有发出),这时数据已经无效。显示器只会依据VSYNC、HSYNC信号来取得、显示数据,并不理会该数据是否有效,何时发出有效的数据由显卡或LCD控制器决定。

VSYNC信号出现的频率表示一秒钟内能显示多少帧图像,称为垂直频率或场频率,这就是我们常说的“显示器频率”;HSYNC信号出现的频率称为水平频率,表示一秒钟能显示多少个像素的数据。 显示器上,一帧数据的存放位置与VSYNC、HSYNC信号的关系如下图所示:

image-20210718155614982

有效数据的行数、列数,即分辨率,它与VSYNC、HSYNC信号之间的距离等,都是可以设置的,这由LCD控制器来完成。

数据组织方式

一幅图像被称为一帧(frame),每帧由多行组成,每行由多个像素组成,每个像素的颜色使用若干位的数据来表示。对于单色显示器,每个像素使用1位来表示,称为1BPP;对于256色显示器,每个像素使用8位来表示,被称为8BPP。

显示器上每个像素的颜色由3部分组成:红(Red)、绿(Green)、蓝(Blue)。它们被称为三基色,这三者的混合几乎可以表示人眼所能识别的所有颜色。比如可以根据颜色的浓烈程度将三基色都分为256个级别,则可以使用255级的红色、255级的绿色、255级的蓝色组合成白色,可以使用0级红色、0级的绿色、0级的蓝色组合成黑色。

LCD控制器可以支持单色(1BPP)、4级灰度(2BPP)、16级灰度(4BPP)、256色(8BPP)的调色板显示模式,支持64K(16BPP)和16M(24BPP)非调色板显示模式。下面介绍64K(16BPP)色显示模式下,图像数据的存储格式。

64K(16BPP)色的显示模式就是使用16位的数据来表示一个像素的颜色。这16位数据的格式又分为两种:5:6:5、5:5:5:1,前者使用高5位来表示红色,中间的6位来表示绿色,低5位来表示蓝色;后者的高15从高到低分成3个5位来表示红、绿、蓝色,最低位来表示透明度。5:5:5:1的格式也被称为RGBA格式(A:Alpha,表示透明度)。

一个4字节可以表示两个16BPP的像素,使用高2字节还是低2字节来表示第一个像素,这也是可以选择的。显示模式为16BPP时,内存数据与像素位置的关系如下:

当BSWP=0、HWSWP=0时,内存中像素的排列格式: 地址D[31:16]D[15:0]00HP1P204HP3P408HP5P6 当BSWP=0、HWSWP=1时,内存中像素的排列格式: 地址D[31:16]D[15:0]00HP2P104HP4P308HP6P5 像素在LCD屏上的排列 image-20210718171819731 像素色值与VD[23:0]引脚的对应关系 VD23222120191817161514131211109876543210RED43210NCNCNCNCNCNCNCNCGREEN543210BLUE43210

输出方式

1、通过frame buffer显示(最典型的方式) image-20210719221548161 上图中可以看到,我们需要在内存里面申请一块内存(此内存被称为frame buffer),之后各种配置LCD控制器,配置显示模式为16PP, 显示模式为5:6:5, 把frame buffer的首地址告诉控制器, 那么控制器就会从frame buffer获取像素值,根据像素的不同值将不同颜色打向LCD屏幕(LCD控制器类似于电子枪, 向玻璃板发不同的光,LCD控制器内部有个DMA通道)对于frameBuffer来讲,每个值对应LCD屏幕的一个像素,如上图,LCD屏分辨率为(480*272),我们可以定义一个数组a[272][480]大小的数组,并把数组首地址告诉LCD控制器, 那么数组每一项对应LCD屏的一个像素, 比如a[0][0]赋值为0xFFE0,对应LCD屏幕的第一个像素显示为黄色 。 2、通过临时调色板显示 image-20210719221941279

这里要解释几点:

什么叫临时调色板 我们根据2440 用户手册的一句话:this register value will be video data at next frame.(在下一帧显示寄存器的值, tips:一帧就一个图像) 可知:临时调色板是一个寄存器,我们向此寄存器写入颜色,那么LCD屏幕下一次显示图像就会是此寄存器中记录的颜色, 即起到了刷屏的作用(整个屏幕都是一个颜色)什么时候起作用以及用途 当使能此寄存器时, 临时调色板起作用, 这时之前配置的功能(如:通过frame buffer显示)就会无效, 因为使能,LCD屏幕会被迅速刷屏,达到了快速刷屏的目的(不需要通过SDRAM中向frameBuffer中所有元素赋值同一个值来实现刷屏 tips:使用SDRAM,本来就慢),如果要恢复之前的配置功能, 即disable临时调色板功能即可 image-20210719222211405 3、通过调色板显示 image-20210719222237700

上图中, 调色板在控制器内部(注意区别临时调色板)是一块儿内存,首地址为0x4D00400, 一共有256个2字节大小(每两个字节表示一个颜色), 上图中,通过配置寄存器告诉LCD控制器调色板的显示格式为RGB565,之后需要手动将此调色板赋值,比如图00H的位置赋值为”黄色”,之后对于framebuffer来讲,其中的每一项代表一个调色板中的索引, 比如frameBuffer的第一项的值为0,则硬件就会自动找调色板中的第一项值, 即将0xFFE0输出, LCD第一个像素点显示黄色 还有一个问题,如何使能调色板功能呢? 我们上面介绍”通过frame buffer显示”中提到,配置寄存器显示模式为16BPP,显示方式是5:6:5,那么控制器就会认为frame buffer中的每一个元素代表的就是颜色的值,并且显示方式是5:6:5, 但是如果我们配置显示模式为8BPP,显示方式是5:6:5, LCD控制器就自动认为用的调色板模式,且调色板中颜色的显示方式为(5:6:5)(这里的8Bpp,代表frame buffer中的每个元素都是8位2进制表示,每个元素的值是调色板中的索引值),那么调色板的应用场合是什么样呢?我们引用网上的一个说明(稍微修改下):

在笔者开发LCD,其显示分辨率设置为640×480。如果色深设置为16 bpp,在系统使用时,画面将会出现明显的抖动、不连贯,这是由于芯片的运算负荷过重造成的。如果按本文中提到的调色板方法,即采用8位色深显示,颜色的选取可以满足需要,画面的显示将明显稳定。这说明,在显示分辨率较高,色彩种类要求比较简单的嵌入式应用中,调色板技术是一个非常值得重视的选择

TFT-LCD的时序

每个VSYNC信号表示一帧数据的开始;每个HSYNC信号表示一行数据的开始,无论这些数据是否有效;每个VCLK信号表示正在传输一个像素的数据,无论它是否有效。数据是否有效只是对CPU的LCD控制器来说的,LCD根据VSYNC、HSYNC、VCLK不停的读取总线数据并显示。

image-20210719223036418

上图中的时序图,分为两个部分,上面部分是一帧的时序图,下面部分是一行的时序图我们分析下时序图中每个参数的意义(上图中的①->⑩) :

对应于上述的过程1,2, VSYNC信号(代表一帧的开始)需要持续一段时间②(VSPW+1), 电子枪认为收到了VSYNC信号(即白扫射了VSPW+1行,也可以说白扫射了(VSPW+1)个HSYNC周期时间),收到信号后,还要继续持续时间③(VBPD+1), LCD控制器才开始发送有效数据, 从而电子枪发射有效像素, 即(② + ③)为LCD屏幕上边的无效区, 对于①参数, 这是手册上的数据, 即告我们默认LCD控制器发送HSYNC信号为高电平,但实际LCD接受HSYNC硬件上有可能设计成低电平有效, 所以可以对寄存器进行修改, 让LCD控制器发出HSYNC控制信号为低电平

tips: VSPW VBPD参数会根据datasheet来具体设置(下文会提到), 设置这些参数的目的是告诉LCD控制器电子枪的反应时间以便发送帧数据(比如电子枪, 发送HSYNC后, 得知道电子枪的反应时间后才开始传送有效数据)

④为, 即有效数据为(LINEVAL+1)行,我们分辨率为480*272,所以LINEVAL为271 *

⑤VFPD+1参数对应于过程5, 当扫描到最后一行结束时(即一帧结束了),LCD控制器不会再发送有效像素数据, 此时电子枪会收游离一段时间(会继续往下白扫好几行(VFPD+1行)无效数据), 这个时间需要告诉LCD控制器,以便控制器知道等待多长时间在发送VSYNC信号,从而进行下一帧的开始

对于⑥、⑦、⑧、⑩三个参数,对应于上述过程3, 接受到HSYNC信号(表示一行的开始)后,此信号必须持续一段时间⑦(HSPW+1个VCLK周期)后, 电子枪才认为信号有效,接受到HSYNC信号后,电子枪还要反应一段时间⑧(白白扫射HBPD+1个VCLK周期后,也可以说发射HBPD+1个无效像素点)后, LCD控制器才开始传送有效数据给电子枪, 当一行扫描结束后,即LCD控制器不发射有效数据了,此时电子枪要游离一段时间⑩(HFPB+1), 这段时间需要告诉LCD控制器,以便让LCD控制器等待此段时间后在发送HSYNC信号从而进行下一行的扫描, 对于⑨参数来说, 分辨率为480*272,所以HOZVAL = 479, 即一行有480个有效数据, 注意有效数据的开始时机, 即需要经历(⑦、⑧)时间后,LCD控制器才开始发送有效数据 。

参数计算

根据LCDdatasheet确认上述参数的值, 下图为AT043TN24数据手册的时序图, 我们很容易对应上面2440手册中LCD的时序图中的参数

image-20210719223335769

上图中已经标注对应关系,就不细说了,强调一点, VSYNC与HSYNC信号都是低电平有效,但是2440手册中LCD时序是高电平有效,所以在配置寄存器时需要注意,要将这两个VSYNC,HSYNC信号设置成低电平有效(极性反转: 默认为高电平,反转后为低电平)

image-20210719223531086

我们可以看到,上图中左边是具体的参数值,Min(最小值), Typ.(典型值), Max(最大值),我们举个例子,在右图中,我们知道关系 VSPW+1 = tvp, 我们在左图中发现tvp的典型值为10, 单位是H(Hsync), 所以VSPW+1 = 10==> VSPW=9, 其余参数的取值都能通过上述方法确定, 还有个问题,VSPW, VSPD,VFBD的时间都依赖于HSYNC周期时间,那么HSYNC周期时间如何确认呢? 查看了下寄存器的设置中好像也没找到相关设置,最后在2440手册中找到这句话

image-20210719223757113

其实意思就是说 LCD控制器会根据电子枪发射像素点的个数来确认HSYNC时间的,比如我们LCD屏幕分辨率是480*272, 当发出VSYNC信号后,要经过VSPW+1反应时间,即VSPW+1个HSYNC周期,我们假设VSPW+1的值为10,那么就是10个HSYNC周期,也就是电子枪扫描了10 x 480个像素点后,LCD控制器就认为经历了10个HSYNC周期时间 。

1、VCLK(Hz) = HCLK/[(CLKVAL+1)*2]

2、VSYNC =1/[ {(VSPW+1)+(VBPD+1)+(LIINEVAL+1)+(VFPD+1)} x {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)} x {2x (CLKVAL+1) / (HCLK )} ]

3、HSYNC = 1/[{(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)} x {2x (CLKVAL+1) / (HCLK )}]

image-20210718175250797

将VSYNC、HSYNC、VCLK等信号的时间参数设置好之后,并将帧内存的地址告诉LCD控制器,它即可自动地发出DMA传输从帧内存中得到图像数据,最终在上述信号的控制下出现在数据总线VD[23:0]上。用户只需要把要显示的图像数据写入帧内存中。

2、LCD控制器REGBANK寄存器组介绍

LCD控制器中REGBANK的17个寄存器可以分为6种,如下表所示:

对于TFT-LCD,一般情况下只需要设置前两种寄存器,即LCDCON和LCDSADDR。

名称说明LCDCON1~LCDCON5用于选择LCD类型,设置各类控制信号的时间特性等LCDSADDR1~LCDSADDR5用于设置帧内存的地址TPAL临时调色板寄存器,可以快速的输出一帧单色的图像LCDINTPND用于LCD的中断,在一般应用中无需中断LCDSRCPND用于LCD的中断,在一般应用中无需中断LCDINTMSK用于LCD的中断,在一般应用中无需中断REDLUT专用于STN-LCDGREENLUT专用于STN-LCDBLUELUT专用于STN-LCDDITHMODE专用于STN-LCDTCONSEL专用于SEC TFT-LCD 2.1、LCD控制寄存器LCDCON1

主要用于选择LCD类型、设置像素时钟、使能LCD信号的输出等,格式如下表所示:

功能位说明LINECNT[27:18]只读,每输出一个有效行其值减一,从LINEVAL减到0;CLKVAL[17:8]用于设置VCLK(像素时钟);MMODE[7]设置VM信号的反转效率,专用于STN-LCD;PNRMODE[6:5]设置LCD类型,对于TFT-LCD设置0b11;BPPMODE[4:1]设置BPP,对于TFT-LCD:0b1100 = 16BPP;ENVID[0]LCD信号输出使能位,0:禁止,1:使能; image-20210718173545208 2.2、LCD控制寄存器LCDCON2

用于设置垂直方向各信号的时间参数,格式如下表所示:

功能位说明VBPD[31:24]VSYNC信号脉冲之后,还要经过(VBPD+1)个HSYNC信号周期,有效的行数据才出现;LINEVAL[23:14]LCD的垂直宽度,(LINEVAL+1)行;VFPD[13:6]一帧中的有效数据完结后,到下一个VSYNC信号有效前的无效行数目:VFPD+1行;VSPW[5:0]表示VSYNC信号的脉冲宽度位(VSPW+1)个HSYNC信号周期,即(VSPW+1)行,这个(VSPW+1)行的数据是无效的; image-20210718173636125 2.3、LCD控制寄存器LCDCON3

用于设置水平方向各信号的时间参数,格式如下表所示:

功能位说明HBPD[25:19]HSYNC信号脉冲之后,还要经过(HBPD+1)个VCLK信号周期,有效的像素数据才出现;HOZVAL[18:8]LCD的水平宽度,(HOZVAL+1)类(像素);HFPD[7:0]一行中的有效数据完结后,到下一个HSYNC信号有效前的无效像素个数,HFPD+1个像素; image-20210718173756266 2.4、LCD控制寄存器LCDCON4

对于TFT-LCD,这个寄存器只用来设置HSYNC信号的脉冲宽度,位[7:0]的数值称为HSPW,表示脉冲宽度位(HSPW+1)个VCLK周期。

image-20210718173853207 2.5、LCD控制寄存器LCDCON5

用于设置各个控制信号的极性,并可从中读到一些状态信息,格式如下表所示:

功能位说明VSTATUS[16:15]只读,垂直状态;00:正处于VSYNC信号脉冲期间;01:正处于VSYNC信号结束到行有效之间;10:正处于有效行期间;11:正处于行有效结束到下一个VSYNC信号之间;HSTATUS[14:13]只读,水平状态;00:正处于HSYNC信号脉冲期间;01:正处于HSYNC信号结束到像素有效之间;01:正处于像素有效期间;11:正处于像素有效结束到下一个HSYNC信号之间;BPP24BL[12]设置TFT-LCD的显示模式为24BPP时,一个4字节中的哪3个字节有效,0:LSB有效,1:MSB有效(高地址的3个字节);FRM565[11]设置TFT-LCD的显示模式为16BPP时,使用的数据格式,0表示5:5:5:1格式,1表示5:6:5格式;INVVCLK[10]设置VCLK信号有效沿极性:0表示在VCLK的下降沿读取数据;1表示在VCLK的上升沿读取数据;INVVLINE[9]设置VINE/HSYNC脉冲的极性;0表示正常极性,1表示反转的极性;INVVFRAME[8]设置VFRAME/VSYNC脉冲的极性;0表示正常极性,1表示反转的极性;INVVD[7]设置VD数据线表示数据的极性;0表示正常极性,1表示反转的极性;INVVDEN[6]设置VDEN信号的极性;0表示正常进行,1表示反转的极性;INVPWREN[5]设置PWREN信号的极性;0表示正常进行,1表示反转的极性;INVLEND[4]设置LEND信号的极性;0表示正常进行,1表示反转的极性;PWREN[3]LCD_PWREN信号输出使能;0表示禁止,1表示使能;ENLEND[2]LEND信号输出使能;0表示禁止,1表示使能;BSWP[1]字节交换使能;0表示禁止,1表示使能;HWSWP[0]半字(2字节)交换使能,0表示禁止,1表示使能; image-20210718174055348 2.6、帧内存地址寄存器LCDSDRR1~LCDSDRR3

帧内存可以很大,而真正要显示的区域被称为视口(view point),它处于帧内存之内,这个3个寄存器用于确定帧内存的起始地址,定位视口在帧内存中的位置。

下图给出了帧内存和视口的位置关系:

image-20210718174314355

下面分别介绍各个帧内存寄存器;

LCDSADRR1寄存器格式 功能位说明LCDBANK[29:21]用于保存帧内存起始地址A[30:22],帧内存起始地址必须为4MB对齐;LCDBASEU[20:0]对于TFT-LCD,用于保存视口所对应的内存起始地址A[21:1],这块内存也被称为LCD的帧缓冲区(frame buffer); image-20210718174852958 LCDSADRR2寄存器格式 功能位说明LCDBASEL[20:0]对于TFT-LCD,用来保存LCD的帧缓冲区结束地址A[21:1],其值可如下计算:LCDBASEL=LCDBASEU+(PAGEWIDTH+OFFSIZE)*(LINEVAL+1)

注意:可以修改LCDBASEU、LCDBASEL的值来实现图像的移动,不过不能在一帧图像的结束阶段进行修改;

image-20210718174924477 LCDSADRR3寄存器格式 功能位说明OFFSIZE[21:11]表示上一行最后一个数据与下一行第一个数据之间地址差值的半字节,即以半字位单位的地址差;0表示两行数据是紧接着的,1表示它们之间相差2个字节,以此类推;PAGEWIDTH[10:0]视口的宽度,以半字位为单位; image-20210718175006960 3、调色板 image-20210718175642065 临时调色板寄存器TPAL

如果要输出一帧单色的图像,可以在TPAL寄存器中设定这个颜色值,然后使能TPAL寄存器,这种方法可以避免修改整个调色板或帧缓冲区。

TPAL寄存器格式:

功能位说明TPALEN[24]调色板寄存器使能位,0禁止,1使能;TPALVAL[23:0]颜色值;TPALVAL[23:16]:红色TPALVAL[15:8]:绿色TPALVAL[7:0]:蓝色

注意:临时调色板寄存器TPAL可以用在任何显示模式下,并非只能用在8BPP模式下。

4、编写驱动 4.1 lcd.c 搭建整体框架

可参考内核自带的相关lcd驱动(drivers/video/),添加头文件:

#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct fb_info *s3c_lcd; static int lcd_init(void) { /* 1. 分配一个fb_info */ s3c_lcd = framebuffer_alloc(0, NULL); /* 2. 设置 */ /* 2.1 设置固定参数 */ /* 2.2 设置可变参数 */ /* 2.3 设置操作函数 */ /* 2.4 设置其它内容 */ /* 3. 硬件相关的操作 */ /* 3.1 配置GPIO用于LCD */ /* 3.2 根据LCD手册设置LCD控制器,例如VCLK频率等 */ /* 3.3 分配显存(frambuffer),并将地址告诉LCD控制器 */ /* 4. 注册 */ register_framebuffer(s3c_lcd); return 0; } static void lcd_exit(void) { } module_init(lcd_init); module_exit(lcd_exit); MODULE_LICENSE("GPL");

入口函数lcd_init()

1、分配一个fb_infos3c_lcd = framebuffer_alloc(0, NULL); 2、 设置 设置固定参数——fb_fix_screeninfo结构体 image-20210718213023752 /* 2.1 设置固定的参数 */ strcpy(s3c_lcd->fix.id, "mylcd"); s3c_lcd->fix.smem_len = 480*272*16/8; //屏幕分辨率480X272,16bpp/pix s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS; //屏幕类型 s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* 真彩TFT */ s3c_lcd->fix.line_length = 480*2; //一行需要的存储长度=480像素X2字节 设置可变参数 image-20210718222829528 /* 2.2 设置可变参数 */ s3c_lcd->var.xres = 480; //X方向的分辨率 s3c_lcd->var.yres = 272; //y方向的分辨率 s3c_lcd->var.xres_virtual = 480; //X方向虚拟的分辨率 s3c_lcd->var.yres_virtual = 272; //y方向的虚拟分辨率 s3c_lcd->var.bits_per_pixel = 16; //每个像素用多少位表示 /* RGB:565 */ s3c_lcd->var.red.offset = 11; //从第11位开始 s3c_lcd->var.red.length = 5; //占5个位 s3c_lcd->var.red.msb_right = 0; //数据在offset的右边吗?默认为0,表示在左边(高位方向)。可以不需设置 s3c_lcd->var.green.offset = 5; //从第5位开始 s3c_lcd->var.green.length = 6; s3c_lcd->var.blue.offset = 0; //从第0位开始 s3c_lcd->var.blue.length = 5; s3c_lcd->var.activate = FB_ACTIVATE_NOW; //不明白,暂用默认值 设置操作函数——fbops s3c_lcd->fbops = &s3c_lcdfb_ops; 在函数外定义fb_ops结构体: static struct fb_ops s3c_lcdfb_ops = { .owner = THIS_MODULE, .fb_setcolreg = s3c_lcdfb_setcolreg, //调色板设置函数 .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, }; 其它的设置 s3c_lcd->pseudo_palette = pseudo_palette; //调色板数组地址 s3c_lcd->screen_base = ; /* 显存的虚拟地址 */ s3c_lcd->screen_size = 480*272*16/8; 3、硬件相关的操作 配置GPIO用于LCD image-20210718230408435 image-20210718231335391

通过原理图可知,所有使用到的引脚均要配置。然后查看原理图,找到各引脚对应的IO端口:

image-20210718230823866 image-20210718231849600

首先在函数外定义用到的IO口的寄存器指针变量:

static volatile unsigned long *gpbcon; static volatile unsigned long *gpbdat; static volatile unsigned long *gpccon; static volatile unsigned long *gpdcon; static volatile unsigned long *gpgcon;

然后在函数体内映射地址:

/*配置GPIO用于LCD*/ //即使你写了仅映射4个字节,系统也还是会映射至少1页(4KB) gpbcon = ioremap(0x56000010, 8); gpbdat = gpbcon+1; gpccon = ioremap(0x56000020, 4); gpdcon = ioremap(0x56000030, 4); gpgcon = ioremap(0x56000060, 4); *gpccon = 0xaaaaaaaa; /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */ *gpdcon = 0xaaaaaaaa; /* GPIO管脚用于VD[23:8] */ /* GPB0设置为输出引脚 */ *gpbcon &= ~(3); *gpbcon |= 1; *gpbdat &= ~1; /* 先输出低电平,使背光电源关闭 */ *gpgcon |= (3 bit[7]:取默认值 * PRNMODE => bit[6:5]: 0b11 (TFT LCD panel) * BPPmode => bit[4:1]: 0b1100(16 bpp for TFT) * ENVID => bit[0] : 0b0 (先暂时禁止,需要时打开.) */ lcd_regs->lcdcon1 = (4fix.smem_len) >> 1) & 0x1fffff; /* Frame Buffer 的有效显示区的宽度(半字,即2字节为单位) * OFFSIZE => bit[21:11]: ,不懂,取默认值 * PAGEWIDTH => bit[10:0]: ,一行的长度(单位: 2字节) */ lcd_regs->lcdsaddr3 = (480*16/16); /* 启动LCD */ lcd_regs->lcdcon1 |= (1= 16 - bf->length; return chan offset; } 设置调色板 static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info) { unsigned int val; if (regno > 16) return 1; /* 用red,green,blue三原色构造出val */ val = chan_to_field(red, &info->var.red); val |= chan_to_field(green, &info->var.green); val |= chan_to_field(blue, &info->var.blue); //((u32 *)(info->pseudo_palette))[regno] = val; pseudo_palette[regno] = val; return 0; } lcd.c文件的整体代码结构 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info); struct lcd_regs { unsigned long lcdcon1; unsigned long lcdcon2; unsigned long lcdcon3; unsigned long lcdcon4; unsigned long lcdcon5; unsigned long lcdsaddr1; unsigned long lcdsaddr2; unsigned long lcdsaddr3; unsigned long redlut; unsigned long greenlut; unsigned long bluelut; unsigned long reserved[9]; unsigned long dithmode; unsigned long tpal; unsigned long lcdintpnd; unsigned long lcdsrcpnd; unsigned long lcdintmsk; unsigned long lpcsel; }; static struct fb_ops s3c_lcdfb_ops = { .owner = THIS_MODULE, .fb_setcolreg = s3c_lcdfb_setcolreg, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, }; static struct fb_info *s3c_lcd; static volatile unsigned long *gpbcon; static volatile unsigned long *gpbdat; static volatile unsigned long *gpccon; static volatile unsigned long *gpdcon; static volatile unsigned long *gpgcon; static volatile struct lcd_regs* lcd_regs; static u32 pseudo_palette[16]; /* from pxafb.c */ static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) { chan &= 0xffff; chan >>= 16 - bf->length; return chan offset; } static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info) { unsigned int val; if (regno > 16) return 1; /* 用red,green,blue三原色构造出val */ val = chan_to_field(red, &info->var.red); val |= chan_to_field(green, &info->var.green); val |= chan_to_field(blue, &info->var.blue); //((u32 *)(info->pseudo_palette))[regno] = val; pseudo_palette[regno] = val; return 0; } static int lcd_init(void) { /* 1. 分配一个fb_info */ s3c_lcd = framebuffer_alloc(0, NULL); /* 2. 设置 */ /* 2.1 设置固定的参数 */ strcpy(s3c_lcd->fix.id, "mylcd"); s3c_lcd->fix.smem_len = 480*272*16/8; s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS; s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR; /* TFT */ s3c_lcd->fix.line_length = 480*2; /* 2.2 设置可变的参数 */ s3c_lcd->var.xres = 480; s3c_lcd->var.yres = 272; s3c_lcd->var.xres_virtual = 480; s3c_lcd->var.yres_virtual = 272; s3c_lcd->var.bits_per_pixel = 16; /* RGB:565 */ s3c_lcd->var.red.offset = 11; s3c_lcd->var.red.length = 5; s3c_lcd->var.green.offset = 5; s3c_lcd->var.green.length = 6; s3c_lcd->var.blue.offset = 0; s3c_lcd->var.blue.length = 5; s3c_lcd->var.activate = FB_ACTIVATE_NOW; /* 2.3 设置操作函数 */ s3c_lcd->fbops = &s3c_lcdfb_ops; /* 2.4 其他的设置 */ s3c_lcd->pseudo_palette = pseudo_palette; //s3c_lcd->screen_base = ; /* 显存的虚拟地址 */ s3c_lcd->screen_size = 480*272*16/8; /* 3. 硬件相关的操作 */ /* 3.1 配置GPIO用于LCD */ gpbcon = ioremap(0x56000010, 8); gpbdat = gpbcon+1; gpccon = ioremap(0x56000020, 4); gpdcon = ioremap(0x56000030, 4); gpgcon = ioremap(0x56000060, 4); *gpccon = 0xaaaaaaaa; /* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */ *gpdcon = 0xaaaaaaaa; /* GPIO管脚用于VD[23:8] */ *gpbcon &= ~(3); /* GPB0设置为输出引脚 */ *gpbcon |= 1; *gpbdat &= ~1; /* 输出低电平 */ *gpgcon |= (3Graphics support ->Support for fame buffer devices ->S3C2440 LCD framebuffer support(需要开启) 6.3 分析s3c2410fb.c中代码(不用修改) //驱动的入口函数,使用platform框架进行匹配 int __init s3c2410fb_init(void) { int ret = platform_driver_register(&s3c2410fb_driver);//在此处进行匹配,s3c2410fb_driver在下一步 if (ret == 0) ret = platform_driver_register(&s3c2412fb_driver); return ret; } //s3c2410fb_driver是设备驱动信息根据driver.name进行匹配的 static struct platform_driver s3c2410fb_driver = { .probe = s3c2410fb_probe, .remove = __devexit_p(s3c2410fb_remove), .suspend = s3c2410fb_suspend, .resume = s3c2410fb_resume, .driver = { .name = "s3c2410-lcd", .owner = THIS_MODULE, }, }; ======================================================================= //当匹配成功时,调用s3c2410fb_probe函数 static int __devinit s3c2410fb_probe(struct platform_device *pdev) { return s3c24xxfb_probe(pdev, DRV_S3C2410);//DRV_S3C2410是type类型,为了区分DRV_S3C2412 } //看一下具体的s3c24xxfb_probe static int __devinit s3c24xxfb_probe(struct platform_device *pdev, enum s3c_drv_type drv_type) { //传入的参数pdev = s3c_device_lcd, /* static struct resource s3c_lcd_resource[] = { [0] = DEFINE_RES_MEM(S3C24XX_PA_LCD, S3C24XX_SZ_LCD), [1] = DEFINE_RES_IRQ(IRQ_LCD), }; struct platform_device s3c_device_lcd = { .name = "s3c2410-lcd", .id = -1, .num_resources = ARRAY_SIZE(s3c_lcd_resource), .resource = s3c_lcd_resource, .dev = { .dma_mask = &samsung_device_dma_mask, .coherent_dma_mask = DMA_BIT_MASK(32), } }; */ struct s3c2410fb_info *info; struct s3c2410fb_display *display; struct fb_info *fbinfo; struct s3c2410fb_mach_info *mach_info; struct resource *res; int ret; int irq; int i; int size; u32 lcdcon1; mach_info = pdev->dev.platform_data;//这里取出来前面存入的东西,就是smdk2440_fb_info,此变量内含各种LCD的参数信息 //找到是哪个display,default_display = 0,num_displays = 1 display = mach_info->displays + mach_info->default_display; irq = platform_get_irq(pdev, 0);//注册LCD中断 //申请一个帧缓冲区结构体,内含有帧缓冲区设备的属性和操作函数的集合 fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev); platform_set_drvdata(pdev, fbinfo);//把fbinfo存入pdev.dev.driver_data,pdev也就是s3c_device_lcd info = fbinfo->par;//fbinfo->par是在framebuffer_alloc()申请时开辟了一块空间 info->dev = &pdev->dev; info->drv_type = drv_type;//drv_type = DRV_S3C2410 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//获得平台资源 size = resource_size(res); info->mem = request_mem_region(res->start, size, pdev->name);//申请内存 info->io = ioremap(res->start, size);//内存映射 if (drv_type == DRV_S3C2412) info->irq_base = info->io + S3C2412_LCDINTBASE; else info->irq_base = info->io + S3C2410_LCDINTBASE;//0x4D000000 + 0x54 = 0x4D000054 strcpy(fbinfo->fix.id, driver_name);//driver_name = s3c2410fb /* Stop the video */ lcdcon1 = readl(info->io + S3C2410_LCDCON1);//读lcdcon1寄存器的内容 writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1); //S3C2410_LCDCON1_ENVID = 1,把lcdcon1中第0位清零。意思是关闭图像输出和lcd 控制器信号输出 fbinfo->fix.type = FB_TYPE_PACKED_PIXELS; fbinfo->fix.type_aux = 0; fbinfo->fix.xpanstep = 0; fbinfo->fix.ypanstep = 0; fbinfo->fix.ywrapstep = 0; fbinfo->fix.accel = FB_ACCEL_NONE; fbinfo->var.nonstd = 0; fbinfo->var.activate = FB_ACTIVATE_NOW; fbinfo->var.accel_flags = 0; fbinfo->var.vmode = FB_VMODE_NONINTERLACED; fbinfo->fbops = &s3c2410fb_ops;//应用层使用open,read,write等操作函数集合 fbinfo->flags = FBINFO_FLAG_DEFAULT; fbinfo->pseudo_palette = &info->pseudo_pal; for (i = 0; i < 256; i++) info->palette_buffer[i] = PALETTE_BUFF_CLEAR;//清除缓冲区 ret = request_irq(irq, s3c2410fb_irq, 0, pdev->name, info);//申请lcd中断 //获取lcd的时钟,并使能时钟 info->clk = clk_get(NULL, "lcd"); clk_enable(info->clk); info->clk_rate = clk_get_rate(info->clk); /* 计算缓存,一帧图像的大小 */ for (i = 0; i < mach_info->num_displays; i++) { unsigned long smem_len = mach_info->displays[i].xres; smem_len *= mach_info->displays[i].yres; smem_len *= mach_info->displays[i].bpp; smem_len >>= 3;//长*宽*bpp(16位) / 8 if (fbinfo->fix.smem_len < smem_len) fbinfo->fix.smem_len = smem_len; } /* Initialize video memory */ ret = s3c2410fb_map_video_memory(fbinfo); fbinfo->var.xres = display->xres; fbinfo->var.yres = display->yres; fbinfo->var.bits_per_pixel = display->bpp; //初始化lcd内部寄存器,主要是gpio,用于数据传输的VD[0:23] s3c2410fb_init_registers(fbinfo); //注册一个帧缓冲实体,也就是fb_info结构体 ret = register_framebuffer(fbinfo); /* create device files */ ret = device_create_file(&pdev->dev, &dev_attr_debug); if (ret) printk(KERN_ERR "failed to add debug attribute\n"); printk(KERN_INFO "fb%d: %s frame buffer device\n", fbinfo->node, fbinfo->fix.id); return 0; }

fbinfo->fbops = &s3c2410fb_ops; //操作函数集合

之后并没有看到注册设备节点,也就是在看到在/dev目录下注册设备。猜测应该在ret = register_framebuffer(fbinfo);中干了什么东西!下面重点看一下register_framebuffer(fbinfo): int register_framebuffer(struct fb_info *fb_info) { //省略了不必要代码 ret = do_register_framebuffer(fb_info); return ret; } =================================================================== static int do_register_framebuffer(struct fb_info *fb_info) { int i; struct fb_event event; struct fb_videomode mode; //遍历register_fb数组中找到一个未用的空项 for (i = 0 ; i < FB_MAX; i++) if (!registered_fb[i]) break; //在此处注册了设备节点,FB_MAJOR = 29,i根据register_fb中第几个空项来确定,如果是第一个就是i = 0,名字fb0/1/2 fb_info->dev = device_create(fb_class, fb_info->device, MKDEV(FB_MAJOR, i), NULL, "fb%d", i); //从此处开始,没看懂 if (fb_info->pixmap.addr == NULL) { fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); if (fb_info->pixmap.addr) { fb_info->pixmap.size = FBPIXMAPSIZE; fb_info->pixmap.buf_align = 1; fb_info->pixmap.scan_align = 1; fb_info->pixmap.access_align = 32; fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; } } fb_info->pixmap.offset = 0; if (!fb_info->pixmap.blit_x) fb_info->pixmap.blit_x = ~(u32)0; if (!fb_info->pixmap.blit_y) fb_info->pixmap.blit_y = ~(u32)0; if (!fb_info->modelist.prev || !fb_info->modelist.next) INIT_LIST_HEAD(&fb_info->modelist); fb_var_to_videomode(&mode, &fb_info->var); fb_add_videomode(&mode, &fb_info->modelist); //以上的不知道要干嘛,但是下面的看懂了,就是把一个帧缓冲实体fb_info填入registered_fb中的一个空项 registered_fb[i] = fb_info; event.info = fb_info; if (!lock_fb_info(fb_info)) return -ENODEV; fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); unlock_fb_info(fb_info); return 0; } 还有fbmem.c文件中没有分析,它里面就是对帧缓冲区的抽象,对应用层提供接口函数。 /** * fbmem_init - init frame buffer subsystem * * Initialize the frame buffer subsystem. * * NOTE: This function is _only_ to be called by drivers/char/mem.c. * */ static int __init fbmem_init(void) { proc_create("fb", 0, NULL, &fb_proc_fops); //注册字符设备,fb_fops是操作函数集合,主设备号:FB_MAJOR = 29,和上面s3c2410fb.c中注册设备节点使用的主设备号一样。 //发现在fbmem_init只是注册设备并没有生成设备节点,只有在一个实际的帧缓冲区也就是lcd设备注册 时候才生成设备节点 if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) printk("unable to get major %d for fb devs\n", FB_MAJOR); fb_class = class_create(THIS_MODULE, "graphics"); return 0; } //fb_fops操作函数集合如下 static const struct file_operations fb_fops = { .owner = THIS_MODULE, .read = fb_read, .write = fb_write, .unlocked_ioctl = fb_ioctl, .mmap = fb_mmap, .open = fb_open, .release = fb_release, .llseek = default_llseek, }; 这些操作函数是给应用调用的 这里简单分析一下应用层如何调用到设备驱动的,以open为例: 应用层:open("/dev/fb0",O_RDWR); || ================================================== 驱动层: || || \/ fb_fops->open(), || || \/ info->fbops->fb_open 根据s3c240fb.c中fbinfo->fbops = &s3c2410fb_ops;故此也就是s3c2410fb_ops函数中的open 虽然s3c2410fb_ops中没有open函数,那就应用层使用open也就是不调用实际的函数。 fbmem.c中 fb_fops->open函数实现如下: static int fb_open(struct inode *inode, struct file *file){ int fbidx = iminor(inode); struct fb_info *info; int res = 0; info = get_fb_info(fbidx); if (!info) { request_module("fb%d", fbidx); info = get_fb_info(fbidx); if (!info) return -ENODEV; } file->private_data = info; if (info->fbops->fb_open) { //这里没运行 res = info->fbops->fb_open(info,1); if (res) module_put(info->fbops->owner); } #ifdef CONFIG_FB_DEFERRED_IO if (info->fbdefio) fb_deferred_io_open(info, inode, file); #endif return res; } 6.4内核配置

在linux-3.4.2/目录下输入“make menuconfig”,配置如下:

# cd linux-3.4.2/ # make menuconfig Device Drivers ---> Graphics support ---> Support for frame buffer devices ---> --- Support for frame buffer devices [*] Enable firmware EDID [ ] Framebuffer foreign endianness support ---- [*] Enable Video Mode Handling Helpers [ ] Enable Tile Blitting Support *** Frame buffer hardware drivers ** < > Epson S1D13XXX framebuffer support S3C2410 LCD framebuffer support [ ] S3C2410 lcd debug messages < > SMSC UFX6000/7000 USB Framebuffer suppor < > Displaylink USB Framebuffer support < > Virtual Frame Buffer support (ONLY FOR TESTING!) < > E-Ink Metronome/8track controller support < > E-Ink Broadsheet/Epson S1D13521 controller suppor [*] Bootup logo ---> --- Bootup logo [ ] Standard black and white Linux logo [ ] Standard 16-color Linux logo [*] Standard 224-color Linux logo 6.5 编译测试 修改好./arch/arm/mach-s3c24xx/mach-smdk2440.c文件后,重新编译生成uImage映像文件。将生成的uImage文件复制到tftp共享文件夹重启开发板,进入菜单,退出菜单。将uImage文件通过tftp方式下载到开发板内存30000000处:tftp 30000000 uImage从30000000处重启开发板:bootm 30000000此时你会在开发板屏幕上看到可爱的小企鹅图片向屏幕输出字符串,可执行echo “hello DarkBirds!” > /dev/tty1


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3