当前位置: 首页 > news >正文

臭臭猫网站建设wordpress注册可见

臭臭猫网站建设,wordpress注册可见,网站代优化,wordpress 存储管理以下内容源于韦东山嵌入式课程的学习与整理#xff0c;如有侵权请告知删除。 之前在博文中学习过LCD#xff08;SoC是S5PV210#xff09;#xff0c;作为对比#xff0c;本文学习S3C2440这款SoC的LCD方面的内容。主要涉及以下三个内容#xff1a; 一、LCD的硬件原理 1.…以下内容源于韦东山嵌入式课程的学习与整理如有侵权请告知删除。 之前在博文中学习过LCDSoC是S5PV210作为对比本文学习S3C2440这款SoC的LCD方面的内容。主要涉及以下三个内容 一、LCD的硬件原理 1.1 LCD与SoC的连接图 随意打开任何一个开发板的原理图如有它接有LCD的话其引脚都是类似的。比如JZ2440开发板中LCD接口的插座如下所示 1.2 简析LCD的操作原理 由上图可知LCD有很多引脚这些引脚如何理解呢我们先看看LCD的操作原理。 如下图所示早期的屏幕背后有一把电子枪它一边移动一边发出各种颜色的光线并打在各个像素上。 这里面有很多细节我们一个个进行梳理 1、电子枪怎么知道它应该移动到下一个像素了向某个像素打出颜色之后应该移动到下一个像素 有一条CLK时钟线与LCD相连。每收到一次CLK信号电子枪就移动一个像素。 2、电子枪向某个像素发出颜色那如何确定这个像素对应什么颜色呢 这个像素的颜色由连接LCD的三组信号线R、G、B来确定的注意是3组线而非3根线。 电子枪会根据RGB三组信号线的数据发出相应的红绿蓝三种颜色的光线由这三种颜色的光线合成这个像素的颜色。 3、电子枪从某一行最左边开始移动一直移动到最右边接下来应该跳到下一行的最左边。电子枪怎么知道它应该跳到下一行最左边 有一条HSYNC水平方向同步信号信号线与LCD相连。每收到一次HSYNC信号脉冲电子枪就跳到下一行最左边。 4、电子枪向最后一个像素发出颜色后应该跳回原点左上角第一个像素点。电子枪怎么知道它应该跳回原点了 有一条VSYNC垂直方向同步信号信号线与LCD相连。每收到一次VSYNC信号脉冲电子枪就跳回原点。 5、上面提到的信号是由谁发出的 上面提到的信号是由LCD控制器发出。 6、RGB三组线的数据从何而来 这些数据肯定是由我们写程序的人提供的比如我们想显示一幅图需要我们提供图片数据。 在内存中划分出一片区域我们管它叫做显存FrameBuffer里面存放着我们要显示的RGB数据LCD控制器从里面将数据读取出来后通过RGB三组线传给电子枪电子枪再依次将颜色打到显示屏上。 7、每个像素在FrameBuffer中占据多少位即BPPBits Per Pixels 在下面的LCD引脚功能图中可以看到有R0~R7、G0~G7、B0~B7共24个引脚所以每个像素共占据8*324位即硬件上LCD的BPP是确定的。但我们可以根据实际情况进行取舍不一定要用完这些线这24条引脚线只是表明这款LCD的最优能力比如JZ2440开发板使用16BPP因此LCD使用R0~R4、G0~G5、B0~B4共16个引脚与SoC相连如本文开头第一张图所示。 1.3 LCD芯片的时序分析 查阅JZ2440开发板所采用的LCD芯片其型号是AT043TN24的数据手册在P5有以下LCD引脚功能图 可见LCD有很多信号这些信号的传输要遵循时序关系。在LCD数据手册P13页有时序图如下 从最小的像素开始分析即首先看CLK从中我们可以看出 1电子枪每次在CLK下降沿对于这款LCD是在CLK下降沿对于其他型号可能是上升沿从数据线Dn7-Dn0上得到数据Dn7-Dn0上的数据来源于前面介绍的FrameBuffer转换成对应的颜色光线并打到像素上然后移动到下一个像素位置。如此循环从一行的最左边一直移动到一行的最右边完成了一行的显示。这里假设一行有x个像素。 2当电子枪打完一行最右边的像素之后就会收到一个水平方向同步信号Hsync。1个Hsync周期由4部分组成thp、thb、thd、thf。thp信号的脉冲宽度不能太小否则电子枪可能识别不到这个信号电子枪正确识别thp信号后会从最右端移动最左端这个移动过程需要耗费时间thb我们称之为移动时间移动的过程中LCD控制器不能发出数据thf表示显示完最右边的像素之后要经过多长时间电子枪才收到Hsync信号并不是一显示完最后一个像素就立马收到Hsync信号。 3当电子枪一行一行地从最左上角原点移动到最右下角后就会收到一个垂直方向同步信号Vsync表示让电子枪跳回原点。1个Vsync周期也是由4部分组成tvp、tvb、tvd、tvf其中tvp是脉冲宽度tvb是移动时间移动的过程中LCD控制器也不能发出数据tvf表示显示完最下面的一行像素之后要经过多长时间电子枪才收到Vsync信号。这里假设一共有y行则LCD的分辨率就是x*y。 4时序图中的参数含义以及它们的典型取值在LCD芯片的数据手册P11有介绍后续我们需要根据典型取值来设置LCD控制器。 这些时序参数会怎样影响显示呢详细内容可以参考该博客其中有一个LCD显示配置示意图 1当电子枪打完一行最右边的像素之后等待HFP时长才收到HSYNC信号当电子枪收到HSYNC信号后电子枪就会从最右边移到最左边这移动过程耗费HBP时长。因此HFP决定了右边黑框HBP决定了左边黑框且值越大则黑框越大。 2当电子枪打完最左下角的像素之后需要等待VFP时长才收到VSYNC信号当电子枪收到VSYNC信号后电子枪就会从最左下角的像素处跳回原点这移动过程耗费VBP时。因此VFP决定了下边黑框VBP决定了上边黑框且值越大则黑框越大。 3我们可以修改HFP、HBP、VFP和VBP的值以调整边框中间灰色区域才是有效数据区域。 1.4 LCD编程的思路 根据上面的分析可以得到LCD编程的思路 1查阅LCD芯片的数据手册得到相关的时序参数、分辨率、引脚极性等信息 2根据从LCD芯片的数据手册得到的信息设置LCD控制器寄存器让其发出正确信号 3在内存中分配一个FrameBuffer明确BPP即明确用多少位来表示一个像素然后把FrameBuffer的首地址告诉LCD控制器 完成上面步骤后LCD控制器就会不断地从FrameBuffer中取出像素数据然后配合其它控制信号将像素数据发送给电子枪电子枪再将颜色显示在LCD上面。以后我们想显示图像时只需要编写程序向FrameBuffer中填入相应数据即可硬件会自动地完成显示操作。 二、S3C2440的LCD控制器 2.1 LCD控制器的功能  几乎所有型号的ARM芯片其LCD控制器的主要功能都是类似的只是寄存器的操作稍有差别。LCD控制器的主要功能包括 1取数据从FrameBuffer中取出像素的数据。 我们需要将FrameBuffer的首地址、BPP、分辨率等信息告知LCD控制器。 2发数据配合其他信号把像素数据发给LCD。 我们需要查阅LCD芯片的数据手册得知LCD芯片的时序要求并根据这些时序要求来设置LCD控制器、设置引脚的极性比如是上升沿还是下降沿有效是低脉冲还是高脉冲有效对于JZ2440所采用的LCD芯片是在CLK的下降沿获取数据Hsync是低脉冲有效。 2.2 LCD控制器的框图 S3C2440数据手册的第15章介绍了LCD控制器如何支持STN和TFT这两种材质的LCD。我们常用LCD都是TFT材质的比如JZ2440开发板采用的LCD就是TFT材质的。在P398有LCD控制器的框图 一旦我们设置好REGBANK一些寄存器的集合LCDCDMA会自动注意它是DMA说明不需要CPU的参与将内存中FrameBuffer里的像素数据通过VIDPRCS发送到引脚VD[23:0]上再配合TIMEGEN生成的各种的控制信号通过REGBANK来设置它们的时序这些像素数据对应的颜色就可以在屏幕上正确地显示出来。 2.3 像素数据的组织格式 S3C2440数据手册的P412列出了像素数据的几种组织格式即像素数据在FrameBuffer中是怎么摆放的。下面是几种常见的组织格式。 1当你设置LCD控制器输出的是24BPP的数据时像素数据的组织格式如下 2当你设置LCD控制器输出的是16BPP的数据时JZ2440开发板正是使用16BPP像素数据的组织格式如下 3当你设置LCD控制器输出的是8BPP的数据时像素数据的组织格式如下 2.4 使用“调色板”  当使用8BPP时会涉及到一个概念调色板。 上面1.2小节中提到“LCD使用R0~R4、G0~G5、B0~B4共16个引脚与SoC相连”于是LCD控制器每次将16位数据传给LCD它认为1个像素的数据有16位那么在FrameBuffer中1个像素的数据就应该占据16位的存储空间这样的话LCD控制器从FrameBuffer中取得1个像素的16位数据后就可以直接由RGB三组线传给LCD不需要调色板的参与。16BPP时刚好符合这种情形如下图所示 假设现在想节省FrameBuffer的空间在FrameBuffer中原本用16位来表示1个像素的数据即16BPP现在改用8位来表示1个像素的数据即8BPP这是否可以呢也是可以的但需要解决一个问题如何由FrameBuffer中8位的数据得到16位的数据这需要引入“调色板”。 作画时我们通常在调色板里先配置好想要的颜色然后用画笔沾到画布上作画。LCD控制器也借用了这个概念从FrameBuffer中获得数据1把这个数据1作为索引相当于数组的下标从调色板相当于数组中获得对应的数据2再把数据2发给电子枪。 由于采用8BPP时某个像素的数据此时称为索引在FrameBuffer中占据8位则调色板中有2^8256项容器每一项容器中存放着16位数据。LCD控制器把FrameBuffer中的8位数据当做索引在调色板中找到它对应的16位数据然后将这16位数据发给电子枪。 调色板是一块特殊的内存在使用8BPP时需要先设置调色板。在S3C2440数据手册中搜索“PALETTE”在P416有以下内容由此可知调色板的起始地址0x4D00_0400这个地址隶属于LCD控制器一共有256项每项占据4个字节但只用到最低2个字节。 采用8BPP时FrameBuffer中存储的不是真正的像素数据因此8BPP也叫伪彩色相对地16BPP或者24BPP叫真彩色。 2.5 让LCD只显示某一种颜色 1对于16BPP和24BPP这些真彩色需要往FrameBuffer中填充同一数值某一种颜色所对应的像素数据。 2对于8BPP可以将调色板设置为同一种颜色导致不同索引对应的颜色都一样也可以往FrameBuffer中填充同一数值。 3S3C22440有一个“临时调色板”的特性一旦使能临时调色板不管FrameBuffer里面是什么数据都只调用临时调色板中的数据。在S3C22440的数据手册P432有以下内容它表明一旦TPAL[24]0b1则只会调用TPAL[23:0]中设定的像素的数据。 2.6 LCD控制器的寄存器 LCD控制器的寄存器组其位含义在S3C2440数据手册P422包括LCDCON1~LCDCON5、LCDSADDR1~LCDSADDR3等寄存器。 通过这些寄存器可以设置像素时钟的频率、LCD材质、BPP、使能LCD控制器可以设置垂直方向、水平方向的相关参数比如时序参数、一共有多少行像素、每行有多少个像素等等可以设置FrameBuffer的基地址等内容。 这里先忽略这部份内容在编程时第5.1节再深入讲解。 三、LCD编程_程序框架 本节主要有两个内容讲解后续程序的框架准备一个支持Nand、Nor启动的程序后续我们将在这程序基础上修改。  3.1 程序框架  LCD编程的框架与对应的.c文件如下图所示尽可能地“高内聚低耦合” 1首先我们需要一个提供操作菜单的测试程序Icd_test.c调用画线、画圆、写字函数。 2我们的目的是在LCD上画线与画图geomentry.c、写字font.c其核心都是画点farmebuffer.c属于纯软件。 3接下来是LCD芯片自身的特性。不同的LCD其配置参数也会不一样时间参数、引脚极性等体现在Icd_3.5.c 或 lcd_4.3.c。 4接下来根据LCD芯片特性来设置S3C2440的LCD控制器体现在s3c2440_Icd_controller.c文件它对S3C2440这款SoC的LCD控制器进行了设置。如果采用其他SoC比如S5PV210则添加这款SoC的LCD控制器设置代码即可比如s5pv210_Icd_controller.c。 5最后是LCD硬件操作。只要设置好上层的内容这部份是硬件自动完成的无需关注。 后续我们将根据这个框架来编写代码例如课程安排如下 3.2 面向对象编程 为了让程序更加好扩展需要“面向对象编程”简单地理解就是定义一个结构体类型用来描述不同LCD芯片的共同属性。 1不同的LCD芯片为LCD的共同属性定义一个结构体 假如有两款不同的LCD芯片比如3.5寸和4.3寸的LCD我们如何在程序中表示它们 首先抽象出3.5寸、4.3寸LCD的共同属性例如都有初始化函数init、LCD的型号名字name则我们可以新建一个上层文件lcd.c在里面更准确地说应该在lcd.h文件中定义一个struct lcd结构体以及一个struct lcd *类型的指针 //lcd.c struct lcd {void (*init)(void);char* name; }; struct lcd *ptr; 然后在下一层文件lcd_3.5.c、lcd_4.3.c中分别定义一个struct lcd类型的变量用来表示对应LCD的属性 //lcd_3.5.c struct lcd lcd_3.5_ptr {.init lcd_3.5_init;.name xxxy; };//lcd_4.3.c struct lcd lcd_4.3_ptr {.init lcd_4.3_init;.name yyy; };如此一来我们只需要根据实际的 LCD 类型将上层文件lcd.c中的ptr指针指向下层文件Icd_3.5.c中的lcd_3.5_ptr变量或者下层文件Icd_4.3.c中的lcd_4.3_ptr变量就可以调用对应的init函数了。以后要添加其他LCD类型时例如7寸的只需要添加这款LCD自身特性层的代码即可比如lcd_7.0.c根本不会影响上一层lcd.c文件中的代码这就是结构化编程好处的简单体现。 2不同SoC的LCD控制器为LCD控制器的共同属性定义一个结构体 假设在上层文件lcd_control.c中其实应该在lcd_control.h定义了一个结构体struct lcd_controller它表示LCD控制器的共同属性 struct lcd_controller{void (*pin_init)(void);void (*timeseq_init)(void);void (*framebuffer_init)(void); } 在下一层文件s5pv210_Icd_controller.c 、s3c2440_Icd_controller.c中都有它的实例化分别表示s5pv210、s3c2440的LCD控制器 //s5pv210_lcd_controller.c文件 struct lcd_controller s5pv210_lcd_controller {.pin_init s5pv210_pin_init;.timeseq_init s5pv210_timeseq_init;.framebuffer_init s5pv210_framebuffer_init; }//s3c2440_lcd_controller.c文件 struct lcd_controller s3c2440_lcd_controller {.pin_init s3c2440_pin_init;.timeseq_init s3c2440_timeseq_init;.framebuffer_init s3c2440_framebuffer_init; } 在上层lcd_control.c文件中定义一个struct lcd_controller* 类型的指针ptr当使用JZ2440开发板时让ptr指针指向s3c2440_Icd_controller这个变量当使用X210开发板时让ptr指针s5pv210_Icd_controller这个变量。这样一来无论我们更换哪款开发板也不会影响上层的代码这就是结构化编程好处的简单体现。 另外假如struct lcd_controller结构体中还有一个name成员则我们可以在上层文件lcd_control.c中根据名字来选择与之匹配的LCD控制器见7.1节内容。这种思想在驱动代码中很常见。 3.3 支持Nand与Nor启动的程序 之前的程序大小都没超过4K因此可以Nor启动或者Nand启动现在的LCD程序大小超过4K了因此需要修改启动部分的代码。 目前还没有学习Nand Flash因此将链接中与Nand Flash有关的代码复制到当前代码中即可主要是5.5小节涉及以下文件 得到下图箭头所指的文件夹它就是我们所准备的支持Nand、Nor启动的程序后续我们将在这程序基础上修改。   四、LCD编程_抽象出重要结构体Icd.h、Icd_controller.h 根据3.1节的框架图我们新建一个lcd文件夹在里面新建以下空白文件 测试层Icd_test.c纯软件framebuffer.c画点、geometry.c画线与画圆、font.c写字LCD芯片自身特性层Icd.c上层屏蔽了lcd_4.3.c的细节、Icd_4.3.c下层LCD控制器层Icd_controller.c上层、s3c2440_lcd_controller.c下层 我们先抽象出LCD芯片参数结构体、LCD控制器结构体如下面4.1、4.2所示。 4.1  struct lcd_params结构体表征LCD芯片参数Icd.h LCD芯片的参数包括引脚极性、时序、BPP、分辨率、FrameBuffer的首地址等内容。我们根据面向对象的思想将这些内容封装成一个结构体struct lcd_params如果是C则是类的对象对于C则是结构体定义在lcd.c对应的头文件Icd.h中从下往上看 //lcd.h文件#ifndef _LCD_H //防止头文件被重复包含 #define _LCD_Henum {NORMAL 0, //正常极性INVERT 1, //反转极性 };//引脚的极性 typedef struct pins_polarity {int vclk; /* normal: 在下降沿获取数据 */int rgb; /* normal: 高电平表示1 */int hsync; /* normal: 高脉冲 */int vsync; /* normal: 高脉冲 */ }pins_polarity, *p_pins_polarity;//时序参数包括垂直方向、水平方向 typedef struct time_sequence {/* 垂直方向 */int tvp; /* vysnc脉冲宽度 */int tvb; /* 上边黑框, Vertical Back porch */int tvf; /* 下边黑框, Vertical Front porch *//* 水平方向 */int thp; /* hsync脉冲宽度 */int thb; /* 左边黑框, Horizontal Back porch */int thf; /* 右边黑框, Horizontal Front porch */int vclk; }time_sequence, *p_time_sequence;typedef struct lcd_params {/* 引脚极性 */pins_polarity pins_pol;/* 时序 */time_sequence time_seq;/* 分辨率, bpp */int xres;int yres;int bpp;/* framebuffer的地址 */unsigned int fb_base;//后续根据讲解的深入还可能添加其他成员比如char* name;}lcd_params, *p_lcd_params;#endif /* _LCD_H */ 然后我们需要在LCD_4.3.c文件中创建一个struct lcd_params结构体的实例表征着4.3寸LCD芯片的参数见第六节的内容。另外在lcd.c文件中见第七节的内容会有一个struct lcd_params*类型的指针指向LCD_4.3.c文件中的实例以屏蔽和封装LCD_4.3.c文件的细节实现结构化编程。 4.1.1 struct pins_polarity结构体引脚的极性 引脚的极性是指上升沿还是下降沿有效、高电平还是低电平有效。 代码中定义了两个宏NORMAL(0)、INVERT(1)。我们设置LCD控制器的寄存器时需要设置某些引脚的极性比如RGB、VSYNC、HSYNC、CLK引脚的极性一般情况下都设置为NORMAL。至于NORMAL是对应着高电平还是低电平有效、是上升沿还是下降沿有效就需要查看LCD芯片的时序图。时序图一般都是NORMAL对应的情形如果你想与之相反将极性设置为INVERT即可。 enum {NORMAL 0, //正常极性INVERT 1, //反转极性 };//引脚的极性 typedef struct pins_polarity {int vclk; /* normal: 在下降沿获取数据 */int rgb; /* normal: 高电平表示1 */int hsync; /* normal: 高脉冲 */int vsync; /* normal: 高脉冲 *///后面可能还会添加其他信号这里只列出部分 }pins_polarity, *p_pins_polarity; 比如JZ2440所采用的LCD是在像素时钟的下降沿获取像素数据见1.3小节的时序分析。在S3C2440数据手册P426有以下内容可知LCDCON5[10]0时表示在像素时钟的下降沿获取像素数据1则表示在上升沿所以要将LCDCON5[10]设置为NORMAL0。 4.1.2 struct time_sequence结构体时序参数 由1.3小节中的时序图可知时序参数包括水平方向的thp、thb、thd、thf垂直方向的tvp、tvb、tvd、tvf。 //时序参数包括垂直方向、水平方向 typedef struct time_sequence {/* 垂直方向 */int tvp; /* vysnc脉冲宽度 */int tvb; /* 上边黑框, Vertical Back porch */int tvf; /* 下边黑框, Vertical Front porch *//* 水平方向 */int thp; /* hsync脉冲宽度 */int thb; /* 左边黑框, Horizontal Back porch */int thf; /* 右边黑框, Horizontal Front porch */int vclk; }time_sequence, *p_time_sequence; 4.2 struct lcd_controller结构体表征LCD控制器Icd_controller.h 该结构体定义在Icd_controller.h文件中包括LCD初始化函数、使能函数表征着LCD控制器的某些行为 #ifndef _LCD_CONTROLLER_H #define _LCD_CONTROLLER_H#include lcd.htypedef struct lcd_controller {void (*init)(p_lcd_params plcdparams);void (*enable)(void);void (*disable)(void); }lcd_controller, *p_lcd_controller;#endif /* _LCD_CONTROLLER_H */ 然后我们需要在s3c2440_lcd_controller.c文件中定义一个struct lcd_controller结构体的实例它表示S3C2440这款SoC的LCD控制器见第五节开头代码的最后部分。另外在lcd_controller.c文件中会有一个struct lcd_controller*类型的指针指向s3c2440_lcd_controller.c文件中的实例以屏蔽与封装s3c2440_lcd_controller.c文件的细节实现结构化编程。  五、LCD编程_初始化LCD控制器s3c2440_lcd_controller.c 4.2小节最后提到我们需要在s3c2440_lcd_controller.c文件中定义一个struct lcd_controller结构体的实例如下所示 struct lcd_controller s3c2440_lcd_controller {.init s3c2440_lcd_controller_init,.enable s3c2440_lcd_controller_enalbe,.disable s3c2440_lcd_controller_disable, }; 接下来我们需要实现这些函数指针所指向的函数。s3c2440_lcd_controller.c文件完整代码如下从下往上看 #define HCLK 100void jz2440_lcd_pin_init(void) {/* 初始化引脚 : 背光引脚 */GPBCON ~0x3;GPBCON | 0x01;/* LCD专用引脚 */GPCCON 0xaaaaaaaa;GPDCON 0xaaaaaaaa;/* PWREN */GPGCON | (38); }/* 根据传入的LCD参数设置LCD控制器 */ void s3c2440_lcd_controller_init(p_lcd_params plcdparams) {int pixelplace;unsigned int addr;jz2440_lcd_pin_init();/* [17:8]: clkval, vclk HCLK / [(CLKVAL1) x 2]* 9 100M /[(CLKVAL1) x 2], clkval 4.5 5* CLKVAL 100/vclk/2-1* [6:5]: 0b11, tft lcd* [4:1]: bpp mode* [0] : LCD video output and the logic enable/disable*/int clkval (double)HCLK/plcdparams-time_seq.vclk/2-10.5;int bppmode plcdparams-bpp 8 ? 0xb :\plcdparams-bpp 16 ? 0xc :\0xd; /* 0xd: 24bpp */LCDCON1 (clkval8) | (35) | (bppmode1) ;/* [31:24] : VBPD tvb - 1* [23:14] : LINEVAL line - 1* [13:6] : VFPD tvf - 1* [5:0] : VSPW tvp - 1*/LCDCON2 ((plcdparams-time_seq.tvb - 1)24) | \((plcdparams-yres - 1)14) | \((plcdparams-time_seq.tvf - 1)6) | \((plcdparams-time_seq.tvp - 1)0);/* [25:19] : HBPD thb - 1* [18:8] : HOZVAL 列 - 1* [7:0] : HFPD thf - 1*/LCDCON3 ((plcdparams-time_seq.thb - 1)19) | \((plcdparams-xres - 1)8) | \((plcdparams-time_seq.thf - 1)0);/* * [7:0] : HSPW thp - 1*/LCDCON4 ((plcdparams-time_seq.thp - 1)0);/* 用来设置引脚极性, 设置16bpp, 设置内存中象素存放的格式* [12] : BPP24BL* [11] : FRM565, 1-565* [10] : INVVCLK, 0 The video data is fetched at VCLK falling edge* [9] : HSYNC是否反转* [8] : VSYNC是否反转* [7] : INVVD, rgb是否反转* [6] : INVVDEN* [5] : INVPWREN* [4] : INVLEND* [3] : PWREN, LCD_PWREN output signal enable/disable* [2] : ENLEND* [1] : BSWP* [0] : HWSWP*/pixelplace plcdparams-bpp 24 ? (0) : |\plcdparams-bpp 16 ? (1) : |\(11); /* 8bpp */LCDCON5 (plcdparams-pins_pol.vclk10) |\(plcdparams-pins_pol.rgb7) |\(plcdparams-pins_pol.hsync9) |\(plcdparams-pins_pol.vsync8) |\(plcdparams-pins_pol.de6) |\(plcdparams-pins_pol.pwren5) |\(111) | pixelplace;/* framebuffer地址 *//** [29:21] : LCDBANK, A[30:22] of fb* [20:0] : LCDBASEU, A[21:1] of fb*/addr plcdparams-fb_base ~(131);LCDSADDR1 (addr 1);/* * [20:0] : LCDBASEL, A[21:1] of end addr*/addr plcdparams-fb_base plcdparams-xres*plcdparams-yres*plcdparams-bpp/8;addr 1;addr 0x1fffff;LCDSADDR2 addr;// }void s3c2440_lcd_controller_enalbe(void) {/* 背光引脚 : GPB0 */GPBDAT | (10);/* pwren : 给LCD提供AVDD */LCDCON5 | (13);/* LCDCON1BIT 0 : 设置LCD控制器是否输出信号 */LCDCON1 | (10); }void s3c2440_lcd_controller_disable(void) {/* 背光引脚 : GPB0 */GPBDAT ~(10);/* pwren : 给LCD提供AVDD */LCDCON5 ~(13);/* LCDCON1BIT 0 : 设置LCD控制器是否输出信号 */LCDCON1 ~(10); }struct lcd_controller s3c2440_lcd_controller {.init s3c2440_lcd_controller_init,.enalbe s3c2440_lcd_controller_enalbe,.disable s3c2440_lcd_controller_disable, }; 5.1 s3c2440_lcd_controller_init函数 首先是s3c2440_lcd_controller_init函数需要我们依次设置LCD控制器的寄存器。  LCD控制器的寄存器组其位含义在S3C2440数据手册P422包括LCDCON1~LCDCON5、LCDSADDR1~LCDSADDR3等寄存器。 通过这些寄存器可以设置像素时钟的频率、LCD材质、BPP、是否使能LCD控制器可以设置垂直方向与水平方向的相关参数比如时序参数、分辨率可以设置FrameBuffer的基地址等内容。 5.1.1 LCDCON1寄存器 该寄存器的位含义如下图所示主要用来设置像素时钟的频率、LCD材质、BPP、是否使能LCD控制器。 1bit[27:18]是只读的数据位不需要设置。 2bit[17:8]用来设置像素时钟的频率注意计算公式中的VCLK才是像素时钟频率 。  我们的LCD材质是TFT所以计算公式为 VCLK HCLK / [(CLKVAL1) x 2] ( CLKVAL ≥ 0 )。 其中HCLK100MHzHCLK用于LCD控制器在链接中被设置为100MHz VCLK是什么意思在S3C2440数据手册P51有以下内容说明它就是像素时钟的频率 像素时钟频率VCLK是多少在LCD芯片的数据手册P11有以下内容说明5MHzVCLK12MHz都行我们选择典型取值9MHz。 根据上面公式可以推算出CLKVAL≈4.6这里取为5则真正的VCLK100/[(51)*2]8.3MHz满足5MHzVCLK12MHz。 也就是说我们直接往bit[17:8]中写入数值5就好但这样就写死了。注意到VCLK是LCD芯片的一个参数见4.1.2小节那可以在程序中根据plcdparams这个传参来设置LCDCON1[17:8]即CLKVAL的值。 由于VCLKplcdparams-time_seq.vclk根据计算公式有plcdparams-time_seq.vclkHCLK/ [(CLKVAL1) x 2]所以可以得到 CLKVAL(HCLK/plcdparams-time_seq.vclk)/2-10.50.5表示向上取整是根据计算结果肉眼写的0.5。 课程的代码最后选择直接往LCDCON1[17:8]写数值5并没有在代码中写出上面的计算公式。 3bit[7]不用管默认值即可。 4bit[6:5]表示外接LCD的材质。这里配置为0b11表示是TFT材质的LCD。TFT和STN的区别可以自行百度或者在配套书籍P197。我们现在用的LCD一般都是TFT材质的。 6bit[4:1]用来设置BPP是多少。需要根据传入的plcdparams-bpp配置为相应的数值假设只支持8BPP、16BPP、24BPP。 7bit[0]表示是否使能LCD控制器1表示使能0表示关闭。这里暂时不设置在s3c2440_lcd_controller_enalbe函数中才设置。 综上所述s3c2440_lcd_controller_init函数中LCDCON1寄存器设置代码如下 //int clkval (double)HCLK/plcdparams-time_seq.vclk/2-10.5;int clkval 5;int bppmode plcdparams-bpp 8 ? 0xb :\plcdparams-bpp 16 ? 0xc :\0xd; /* 0xd: 24,32bpp */LCDCON1 (clkval8) | (35) | (bppmode1) ;//外接LCD材质 5.1.2 LCDCON2寄存器 该寄存器的位含义如下图所示主要用来设置垂直方向有关的时序参数垂直方向的移动时间、LCD芯片一共有多少行像素、垂直方向的等待时间、垂直方向同步信号的脉冲宽度。  要理解与设置上图四个参数的含义需要对比S3C2440的LCD控制器时序图见数据手册P418、具体LCD芯片的时序图见1.3节。 由此可知VSPW1tvptvp表示垂直方向同步信号的脉冲宽度VBPD1tvbtvb表示垂直方向的移动时间LINEVAL1lineslines表示LCD一共有lines行像素VFPD1tvftvf表示垂直方向的等待时间则根据传参plcdparams可以得到各个量的设置值 bit[31:24]      VBPD          tvb-1        plcdparams-time_seq.tvb - 1bit[23:14]      LINEVAL     lines-1      plcdparams-yres - 1bit[13:6]        VFPD          tvf-1         plcdparams-time_seq.tvf - 1bit[5:0]          VSPW         tvp-1        plcdparams-time_seq.tvp - 1 综上所述s3c2440_lcd_controller_init函数中LCDCON2寄存器设置代码如下 LCDCON2 ((plcdparams-time_seq.tvb - 1)24) | \((plcdparams-yres - 1)14) | \((plcdparams-time_seq.tvf - 1)6) | \((plcdparams-time_seq.tvp - 1)0); 5.1.3 LCDCON3寄存器 该寄存器的位含义如下图所示主要用来设置水平方向的时序参数水平方向移动时间、每行有多少个像素、水平方向的移动时间。 图中参数的含义需要对比S3C2440的LCD控制器时序图、具体LCD芯片的时序图见LCDCON2寄存器中的图。由此可得 bit[25:19]   HBPD        thb水平方向的移动时间-1   plcdparams-time_seq.thb - 1bit[18:8]     HOZVAL    列数每行有多少个像素-1    plcdparams-xres - 1bit[7:0]       HFPD        thf水平方向的等待时间-1    plcdparams-time_seq.thf - 1 综上所述s3c2440_lcd_controller_init函数中LCDCON3寄存器设置代码如下 LCDCON3 ((plcdparams-time_seq.thb - 1)19) | \((plcdparams-xres - 1)8) | \((plcdparams-time_seq.thf - 1)0); 5.1.4 LCDCON4寄存器 该寄存器的位含义如下图所示主要用来设置水平方向同步信号的脉冲宽度。  对比S3C2440的LCD控制器时序图、具体LCD芯片的时序图可知bit[7:0] HSPW thp - 1  plcdparams-time_seq.thp - 1 因此s3c2440_lcd_controller_init函数中LCDCON4寄存器设置代码如下 LCDCON4     ((plcdparams-time_seq.thp - 1)0);  5.1.5 LCDCON5寄存器 该寄存器的位含义如下图所示主要用来设置像素数据在内存中的摆放方式LSB还是MSB、采用16BPP时的格式5:6:5还是5:5:5:I、引脚信号的极性比如像素时钟等信号的极性、是否为LCD供电注意bit[3]是使能bit[5]是设置极性。 1bit[12]当使用24BPP时用来设置像素数据在内存中的摆放方式是LSB还是MSB。 在S3C2440数据手册中搜索“BPP24BL”在P412有以下内容 对于24BPP我们选择将像素数据放在4字节中的低3字节因此需要设置BPP24BLbit[12]0、BSWPbit[1]0、HWSWPbit[0]0。 由于bit[12]默认值就是0所以在代码中对于24BPP没有设置bit[12]0只设置bit[1:0]0b000BSWP0、HWSWP0 //pixelplace 表示bit[1:0]的值 pixelplace plcdparams-bpp 24 ? (0) : |\plcdparams-bpp 16 ? (1) : |\(11); /* 8bpp */ 2bit[11]当使用16BPP时用来设置是5:6:5格式还是5:5:5:I格式。这里我们设置为5:6:5格式。 3bit[10]用来设置像素时钟的极性设置是在上升沿取数据还是在下降沿取数据。 4bit[9]用来设置水平方向同步信号即HSYNC的极性高电平还是低电平有效。 5bit[8]用来设置垂直方向同步信号即VSYNC的极性。 6bit[7]用来设置RGB数据即video data的极性。 7bit[6]用来设置VDEN引脚信号的极性。 由下面的原理图可知VM引线接到了LCD芯片的DE引脚。我们在LCD芯片的数据手册中搜索“DE”在P6得知它是“DATA ENABLE”的含义那么DE引脚应该对应着S3C2440的VDEN引脚原理图中引脚的名字起得很怪异按理说原理图中引脚的名字应该引用S3C2440数据手册P49中的名字比如这里的VM应该写为VDEN才对。 因此我们需要设置VDEN引脚即设置LCDCON5寄存器的bit[6]为此我们在 struct pins_polarity 结构体中添加一个成员de ​//引脚的极性 typedef struct pins_polarity {int vclk; /* normal: 在下降沿获取数据 */int rgb; /* normal: 高电平表示1 */int hsync; /* normal: 高脉冲 */int vsync; /* normal: 高脉冲 *///后面可能还会添加其他信号这里只列出部分int de; /* normal: 高脉冲 */ }pins_polarity, *p_pins_polarity; 在S3C2440的数据手册中查找“VDEN”找到它的时序图可知它的normal对应着高电平 8bit[5]用来设置LCD_PWREN信号的极性。bit[5]和bit[3]都是在设置LCD_PWREN引脚不过bit[5]设置极性bit[3]设置使能。 9bit[4]用来设置LEND信号的极性似乎没用到不需要设置。 10bit[3]用来设置LCD_PWREN引脚是否使能即是否为LCD提供电源。注意它和LCD背光灯是不同的含义。 我们查看原理图在V2原理图中搜索“LCD_PWREN”看一下LCD_PWREN这个引脚是否接到了LCD 由此可知S3C2440的LCD_PWREN引脚与GPG4复用间接地连接到了LCDAVDD?。所以我们需要设置这个引脚为此我们在struct pins_polarity结构体中添加一个成员pwren在s3c2440_lcd_controller_enable函数中我们直接将这个位设置为1因此这里添加这个成员好像也没啥用 ​//引脚的极性 typedef struct pins_polarity {int vclk; /* normal: 在下降沿获取数据 */int rgb; /* normal: 高电平表示1 */int hsync; /* normal: 高脉冲 */int vsync; /* normal: 高脉冲 *///后面可能还会添加其他信号这里只列出部分int de; /* normal: 高脉冲 */int pwren; /* normal: 高脉冲 */ }pins_polarity, *p_pins_polarity; 在S3C2440的数据手册中查找“LCD_PWREN”找到它的时序图可知它的normal对应着高电平 11bit[2]用来设置是否使能LEND引脚似乎没用到不需要设置。 12bit[1]、bit[0]当BPP16或者8时由这两个位决定像素数据在内存中的摆放格式当BPP24时还需要配合bit[12]来决定。 下图中的红框表示编程时的我们设置情况。 综上所述s3c2440_lcd_controller_init函数中LCDCON5寄存器设置代码如下  pixelplace plcdparams-bpp 24 ? (0) : |\plcdparams-bpp 16 ? (1) : |\(11); /* 8bpp */LCDCON5 (plcdparams-pins_pol.vclk10) |\(plcdparams-pins_pol.rgb7) |\(plcdparams-pins_pol.hsync9) |\(plcdparams-pins_pol.vsync8) |\(plcdparams-pins_pol.de6) |\(plcdparams-pins_pol.pwren5) |\ //这里为什么不用设置bit[3](111) | pixelplace; //来开启LCD电源 注意本函数还没有设置bit[3]因为本函数都是与初始化相关的内容不应该包括enable功能在s3c2440_lcd_controller_enalbe函数中才设置bit[3]该函数符合enable的含义。 5.1.6 LCDSADDR1寄存器 LCDSADDR1寄存器的位含义如下主要用来设置FrameBuffer的首地址。 假设FrameBuffer首地址是A[31:0]则LCDSADDR1[29:21]存储A[30:22]LCDSADDR1[20:0]存储A[21:1]。  即LCDSADDR1[29:0] 与 A[ 30:1] 是一一对应的。在编程时我们需要将A[30:1]赋值给LCDSADDR1寄存器体现在下面这段代码 addr plcdparams-fb_base ~(131); LCDSADDR1 (addr 1);//上面取出A[30:0]的数据需要右移1bit得到A[30:1] 这里提一下JZ2440开发板上的LCD是 single-scan LCD。   5.1.7 LCDSADDR2寄存器 LCDSADDR2寄存器的位含义如下主要用来设置FrameBuffer的结束地址。  假设FrameBuffer的结束地址是A[31:0]则LCDSADDR2[20:0]存储A[21:1]。 注意上图给出的计算方法可能有错课程说的可以根据S3C2440数据手册P429的例子自己来算如下所示 又或者根据常识来写FB结束地址 FB首地址 (一共有多少个像素*每个像素占据多少位) / 8 。 因此LCDSADDR2寄存器的设置代码如下 //addr fb首地址 x*y*BPP/8 addr plcdparams-fb_base plcdparams-xres*plcdparams-yres*plcdparams-bpp/8; addr 1; //addr A[21:0] addr A[21:1] addr 0x1fffff; // LCDSADDR2 addr; 5.1.8 其他引脚的设置 1设置LCD背光灯电源 原理图中还有LED、LED-引脚我们在V2原理图中搜索“LED”进而继续搜索“KEYBOARD” 由此可知GPB0控制着LCD背光灯因此需要设置GPBCON寄存器。我们把GPB0配置成输出模式如下所示 /* 初始化背光引脚设置为输出模式 */GPBCON ~0x3; //清零GPBCON | 0x01; //设置为输出模式 2设置LCD电源LCD_PWREN引脚 由5.1.510可知LCD_PWREN引脚与GPG4复用因此需要设置GPGCON寄存器。 我们把GPG4配置成LCD_PWREN功能如下所示 /* PWREN */GPGCON | (38); 3设置LCD的专用引脚如VD 由下面的原理图可知S3C2440中VD[23:0]与GPCx、GPDx引脚复用另外像VFRAM、VLINE等引脚也与GPCx引脚有关所以我们需要分别设置GPCCON、GPDCON寄存器。 GPCCON、GPDCON寄存器的位含义如下图所示 我们把GPCCON、GPDCON寄存器都设置为0xaaaa_aaaa即可如下所示 /* 设置LCD的专用引脚 */GPCCON 0xaaaaaaaa; //0b 1010 1010 …… 1010 0x a a …… a 共8个aGPDCON 0xaaaaaaaa; 综合1~3所述其他引脚的设置代码如下所示 void jz2440_lcd_pin_init(void) {/* 设置背光引脚 */GPBCON ~0x3;GPBCON | 0x01;/* 设置LCD的专用引脚 */GPCCON 0xaaaaaaaa;GPDCON 0xaaaaaaaa;/* 设置LCD电源引脚 */GPGCON | (38); } 然后我们在s3c2440_lcd_controller_init函数开始位置调用这个jz2440_lcd_pin_init函数即可。注意这里只进行初始化不涉及开启所以都不涉及GPXDAT与GPXDAT有关的代码在下面s3c2440_lcd_controller_enalbe函数中。 5.2 s3c2440_lcd_controller_enalbe函数 此函数负责开启某些开关比如开启背光灯、为LCD供电、使能LCD控制器  void s3c2440_lcd_controller_enalbe(void) {/* 背光引脚 : GPB0 */GPBDAT | (10);/* pwren : 给LCD提供AVDD */LCDCON5 | (13); //bit[3]1/* LCDCON1BIT 0 : 设置LCD控制器是否输出信号 */LCDCON1 | (10); //bit[0]1 }5.3 s3c2440_lcd_controller_disable函数 此函数与 s3c2440_lcd_controller_enalbe 函数功能相反 void s3c2440_lcd_controller_disable(void) {/* 背光引脚 : GPB0 */GPBDAT ~(10);/* pwren : 给LCD提供AVDD */LCDCON5 ~(13);/* LCDCON1BIT 0 : 设置LCD控制器是否输出信号 */LCDCON1 ~(10); } 六、LCD编程_LCD芯片参数的设置 lcd_4.3.c 4.1节提到我们需要在lcd_4.3.c文件中创建一个lcd_params类型的实例表征4.3寸LCD芯片的参数作为参数传入s3c2440_lcd_controller.c文件中的s3c2440_lcd_controller_init函数这样就可以初始化LCD控制器了。 在4.1节中已经定义了lcd_params结构体如下所示我们需要确定它的实例中各个成员的值。 typedef struct lcd_params {pins_polarity pins_pol; /* 引脚极性 */time_sequence time_seq; /* 时序 */ /* 分辨率, bpp */int xres;int yres;int bpp; unsigned int fb_base; /* framebuffer的地址 *///后续根据讲解的深入还可能添加其他成员比如char* name;}lcd_params, *p_lcd_params; 通过对比S3C2440的LCD控制器的时序图、JZ2440的4.3寸LCD的时序图、时序参数推荐值可知lcd_4.3.c文件代码设置如下  #define LCD_FB_BASE 0x33c00000lcd_params lcd_4_3_params {.name lcd_4.3//后面会说要加这个这里先提前列出吧.pins_polarity {.de NORMAL, /* normal: 高电平时可以传输数据 */.pwren NORMAL, /* normal: 高电平有效 */.vclk NORMAL, /* normal: 在下降沿获取数据 */.rgb NORMAL, /* normal: 高电平表示1 */.hsync INVERT, /* normal: 高脉冲 */.vsync INVERT, /* normal: 高脉冲 */},.time_sequence {/* 垂直方向 */.tvp 10, /* vysnc脉冲宽度 */.tvb 2, /* 上边黑框, Vertical Back porch */.tvf 2, /* 下边黑框, Vertical Front porch *//* 水平方向 */.thp 41, /* hsync脉冲宽度 */.thb 2, /* 左边黑框, Horizontal Back porch */.thf 2, /* 右边黑框, Horizontal Front porch */.vclk 9, /* MHz */},.xres 480,.yres 272,.bpp 16,.fb_base LCD_FB_BASE, }; 以下是对上面代码的一些解释说明 1首先是设置引脚极性。S3C2440数据手册中的时序图其电平情形就属于normalLCD芯片自身也有一个时序图如果某引脚的电平和S3C2440的一样都是高电平或者低电平有效那么就把该引脚的极性设置为normal如果和S3C2440是相反的则把引脚的极性设置为invert。 从5.1.5中可知VDEN、LCD_PWREN的引脚极性是normal从LCD控制器时序图可知VCLK的引脚极性也是normal至于RGB数据一般来说也不需要反转所以其引脚极性是normal。最后由下面两张图可知需要把hsync、vsync设置为INVERT 2然后是时序参数的值。在LCD芯片数据手册P11中有以下内容 从上图可以得知除了 .bpp这里设置为16、.fb_base的所有取值。 3.fb_base的取值为多少呢我们需要看一下start.S文件中是如何规划内存的使用的。经过分析设置FB的首地址为0x33c00000。 lcd_4.3.c文件完整代码如下 #define LCD_FB_BASE 0x33c00000lcd_params lcd_4_3_params {.name lcd_4.3.pins_polarity {.de NORMAL, /* normal: 高电平时可以传输数据 */.pwren NORMAL, /* normal: 高电平有效 */.vclk NORMAL, /* normal: 在下降沿获取数据 */.rgb NORMAL, /* normal: 高电平表示1 */.hsync INVERT, /* normal: 高脉冲 */.vsync INVERT, /* normal: 高脉冲 */},.time_sequence {/* 垂直方向 */.tvp 10, /* vysnc脉冲宽度 */.tvb 2, /* 上边黑框, Vertical Back porch */.tvf 2, /* 下边黑框, Vertical Front porch *//* 水平方向 */.thp 41, /* hsync脉冲宽度 */.thb 2, /* 左边黑框, Horizontal Back porch */.thf 2, /* 右边黑框, Horizontal Front porch */.vclk 9, /* MHz */},.xres 480,.yres 272,.bpp 16,.fb_base LCD_FB_BASE, };void lcd_4_3_add(void) {register_lcd(lcd_4_3_params); } 七、LCD编程_添加中间层文件lcd.c、lcd_controller.c 在第五、六节我们编写了LCD控制器相关的设置代码s3c2440_lcd_controller.c、LCD芯片参数相关设置代码lcd_4.3.c。 7.1 编写Icd_controller.c文件 接下来我们需要编写一个中间层文件Icd_controller.c它负责接收上一层的LCD芯片参数即lcd_4.3.c文件然后利用这些参数来设置同层下一级的LCD控制器即s3c2440_lcd_controller.c文件。该文件的核心逻辑如下后面需要修改但逻辑是如此 //lcd_controller.c文件 void lcd_controller_init(p_lcd_params plcdparams) {//s3c2440_lcd_controller.init(plcdparams);//调用S3C2440这款SoC的LCD控制器初始化函数selected_lcd_controller.init(plcdparams);//上的写法没有分层思想应该写成与具体SoC无关的代码。 } Icd_controller.c文件的完整代码如下 #define LCD_CONTROLLER_NUM 10 //假设最多支持10款SoC的LCD控制器//指针数组p_array_lcd_controller后续各个LCD控制器注册到该数组中 static p_lcd_controller p_array_lcd_controller[LCD_CONTROLLER_NUM];//表示我们要设置哪款LCD控制器通过名字来找到它 static p_lcd_controller g_p_lcd_controller_selected;//下面这个函数会被某个LCD控制器文件调用用来将LCD控制器注册到数组中 //比如s3c2440_lcd_controller.c会调用该函数将s3c2440的LCD控制器注册到数组中 int register_lcd_controller(p_lcd_controller plcdcon) {int i;for (i 0; i LCD_CONTROLLER_NUM; i)//遍历哪个地方空的就注册到该地方{if (!p_array_lcd_controller[i])//如果是空的{p_array_lcd_controller[i] plcdcon;return i;}}return -1; }//下面这个函数通过名字来查找对应的LCD控制器 int select_lcd_controller(char *name) {int i;for (i 0; i LCD_CONTROLLER_NUM; i){if (p_array_lcd_controller[i] !strcmp(p_array_lcd_controller[i]-name, name)){g_p_lcd_controller_selected p_array_lcd_controller[i];return i;}}return -1; }/* 向上: 接收不同LCD的参数* 向下: 使用这些参数设置对应的LCD控制器*/int lcd_controller_init(p_lcd_params plcdparams) {/* 调用所选择的LCD控制器的初始化函数 */if (g_p_lcd_controller_selected){g_p_lcd_controller_selected-init(plcdparams);return 0;}return -1; }void lcd_contoller_add(void) {s3c2440_lcd_contoller_add();//如果要更换其他SoC需要在这里换一个函数 } 下面是对这段代码的一些解释说明解释Icd_controller.c文件如何管理下一级LCD控制器 1首先在Icd_controller.c文件中设置一个指针数组p_array_lcd_controller用数组来保存同层下一级的各种LCD控制器数组的每个成员都是一个指向LCD控制器结构体的指针用来指向LCD控制器结构体的某个实例。 由于这些结构体需要通过名字来区分所以需要在lcd_controller.h文件中的struct lcd_controller结构体定义中添加成员name typedef struct lcd_controller {char *name;//新添void (*init)(p_lcd_params plcdparams);void (*enable)(void);void (*disable)(void); }lcd_controller, *p_lcd_controller; 并且在s3c2440_lcd_controller.c文件中设置其值为“s3c2440” struct lcd_controller s3c2440_lcd_controller {.name s3c2440,//添加这一行.init s3c2440_lcd_controller_init,.enable s3c2440_lcd_controller_enalbe,.disable s3c2440_lcd_controller_disable, }; 2Icd_controller.c文件提供了一个注册函数register_lcd_controller用来注册LCD控制器 //下面这个函数会被某个LCD控制器调用用来将LCD控制器注册到数组p_array_lcd_controller中 //比如在s3c2440_lcd_controller.c中调用该函数将s3c2440的LCD控制器注册到数组中 int register_lcd_controller(p_lcd_controller plcdcon) {int i;for (i 0; i LCD_CONTROLLER_NUM; i)//遍历哪个地方空的就注册到该地方{if (!p_array_lcd_controller[i])//如果是空的{p_array_lcd_controller[i] plcdcon;return i;}}return -1; } 与Icd_controller.c同层的下一级代码即各个LCD控制器对应的文件比如s3c2440_lcd_controller.c需要通过调用该函数将自己注册到p_array_lcd_controller数组中如下所示这里将该函数封装成s3c2440_lcd_contoller_add函数在同层上一级代码lcd_controller.c中肯定会调用s3c2440_lcd_contoller_add函数例如通过调用lcd_contoller_add函数来间接调用它 //位于s3c2440_lcd_contoller.c文件中 void s3c2440_lcd_contoller_add(void) {register_lcd_controller(s3c2440_lcd_controller); } 3Icd_controller.c文件提供了一个select_lcd_controller函数或者说接口如下所示 int select_lcd_controller(char *name) {int i;for (i 0; i LCD_CONTROLLER_NUM; i){if (p_array_lcd_controller[i] !strcmp(p_array_lcd_controller[i]-name, name)){g_p_lcd_controller_selected p_array_lcd_controller[i];return i;}}return -1; } 上层代码lcd.c文件见7.2节内容中通过调用这个函数来选择某个LCD控制器。如何选择呢根据传参name来匹配。 7.2 编写lcd.c文件 lcd.c文件的完整代码如下 #define LCD_NUM 10 //假设最多支持10款LCD芯片static p_lcd_params p_array_lcd[LCD_NUM];//指针数组 static p_lcd_params g_p_lcd_selected;//表示选中哪款LCD芯片int register_lcd(p_lcd_params plcd)//注册LCD芯片 {int i;for (i 0; i LCD_NUM; i){if (!p_array_lcd[i]){p_array_lcd[i] plcd;return i;}}return -1; }int select_lcd(char *name)//根据名字来选中该款LCD芯片 {int i;for (i 0; i LCD_NUM; i){if (p_array_lcd[i] !strcmp(p_array_lcd[i]-name, name)){g_p_lcd_selected p_array_lcd[i];return i;}}return -1; }int lcd_init(void) {/* 注册LCD */ //这里注册4.3寸这款LCD芯片lcd_4_3_add(); //位于lcd_4.3.c文件中/* 注册LCD控制器 */ //这里注册s3c2440这款SoC的LCD控制器lcd_contoller_add(); //位于lcd_controller.c文件中/* 选择某款LCD */select_lcd(lcd_4.3); //位于lcd.c文件中/* 选择某款LCD控制器 */select_lcd_controller(s3c2440); //位于lcd_controller.c文件中/* 使用LCD的参数, 初始化LCD控制器 */lcd_controller_init(g_p_lcd_selected);//位于lcd_controller.c文件中 } 可见lcd.c文件的思路与编写lcd_controller.c文件是一样的。  1lcd.c文件中也有一个指针数组p_array_lcd它的每个元素都是指针用来指向存储各个LCD芯片参数结构体的实例 2lcd.c文件中也有一个register_lcd函数。同层下一级的各个LCD芯片参数对应的文件比如lcd_4.3.c文件需要通过调用该函数将LCD芯片注册到p_array_lcd数组中。 由此可知p_array_lcd、register_lcd都是供同层下一级文件使用的那向上层提供了什么提供了select_lcd函数。 3lcd.c文件中也有一个select_lcd函数。上一层纯软件层文件通过调用该函数由代码可知不是直接调用该函数而是调用包含该函数的lcd_init函数根据传入的name来选择某款LCD芯片。 4纯软件层只访问到lcd.c文件只使用到lcd.c文件中的函数不关心它之后的下层文件不关心LCD控制器层的文件按理也不关心lcd_4.3.c文件所以不应该用到lcd_4.3.c文件中的函数但这里的代码似乎写得不是很完美所以还是用到了lcd_4.3.c文件中的函数。在lcd.c文件最后写了一个初始化函数lcd_init。该函数会被上一层纯软件层调用。 int lcd_init(void) {/*步骤1能够选择某款LCD、某款LCD控制器的前提是先注册到对应数组中。*如果有多款则可以注册多款然后在2中再选择。由于分别只有一款所以这里分别只列一个函数。*///注册LCD芯片这里是注册4.3寸这款LCD芯片/*lcd_4_3_add函数定义在lcd_4.3.c文件中出现在这里不符合分层思想*我觉得应该在lcd.c文件中写一个lcd_add函数该函数调用lcd_4.3.c文件中的lcd_4_3_add函数*如果有多款LCD芯片要注册可以在lcd_add函数中继续添加其他lcd_x.x.c文件中的注册函数*比如下面的lcd_contoller_add就是这样处理的。*///lcd_add();//我觉得应该写成这样lcd_4_3_add();//注册LCD控制器这里是注册s3c2440这款SoC的LCD控制器/*这里原本的代码是调用s3c2440_lcd_contoller_add函数的*但这样写不好因为该函数位于s3c2440_lcd_contoller.c文件出现在这里不符合分层思想*所以这里改为调用lcd_controller.c文件中的lcd_contoller_add函数*然后lcd_contoller_add函数再调用s3c2440_lcd_contoller_add函数。*///s3c2440_lcd_contoller_add();//不符合分层思想lcd_contoller_add(); //位于lcd_controller.c文件中/*步骤2选择某款LCD、某款LCD控制器*///选择某款LCDselect_lcd(lcd_4.3); //位于lcd.c文件中//选择某款LCD控制器 select_lcd_controller(s3c2440); //位于lcd_controller.c文件中/*步骤3选择某款LCD、某款LCD控制器之后才能使用LCD的参数来初始化LCD控制器*/lcd_controller_init(g_p_lcd_selected); //位于lcd_controller.c文件中 } 如此一来如果后面我们想要添加其他LCD芯片比如7寸的LCD或者更改LCD控制器比如更改为s5pv210的LCD控制器只需要在LCD芯片特性层添加一个lcd_7.0.c文件、在LCD控制器层添加一个s5pv210_lcd_controller.c文件并分别注册到相应数组中然后在lcd.c中的lcd_init函数里通过传入名字参数来选择某款LCD芯片和某款LCD控制器即可。 八、LCD编程_测试层文件lcd_test.c 8.1 编写测试层文件lcd_test.c  为了测试上面编写的代码代码是否出错需要有测试代码我们需要编写测试层文件lcd_test.c该文件内容如下 void lcd_test(void) {unsigned int fb_base;int xres, yres, bpp;int x, y;unsigned short *p;/* 初始化LCD */lcd_init();/* 使能LCD */lcd_enable();/* 获得LCD的参数: fb_base, xres, yres, bpp */get_lcd_params(fb_base, xres, yres, bpp);/* 往framebuffer中写数据 */if (bpp 16){/* 让LCD输出整屏的红色 *//* 565: 0xf800 */p (unsigned short *)fb_base;for (y 0; y yres; y)for (x 0; x xres; x)*p 0xf800;} } 下面是对这段代码的一些说明 1首先调用lcd.c文件中的lcd_init函数见7.2小节对LCD控制器进行初始化核心是lcd_init函数的步骤3。 2然后调用lcd.c文件中的lcd_enable函数来使能LCD。该函数内部调用lcd_controller.c文件中的lcd_controller_enable函数最终调用s3c2440_lcd_controller.c文件中的s3c2440_lcd_controller_enalbe函数。 //位于lcd.c文件 void lcd_enable(void) //对lcd_controller_enable进行封装 {lcd_controller_enable(); }void lcd_disable(void) {lcd_controller_disable(); } 这里为什么要封装一下这是因为我们希望纯软件层这里包括测试层、纯软件的文件只访问到它下一层LCD芯片特性层的文件对应lcd.c文件不去访问LCD控制器层的文件对应lcd_controller.c文件所以我们在lcd.c文件中将lcd_controller.c文件中的lcd_controller_enable函数封装为lcd_enable函数。 3接着调用lcd.c文件中的get_lcd_params函数来获取LCD芯片的参数因为后续4要根据这些参数进行显示 //位于lcd.c文件中 void get_lcd_params(unsigned int *fb_base, int *xres, int *yres, int *bpp) {*fb_base g_p_lcd_selected-fb_base;*xres g_p_lcd_selected-xres;*yres g_p_lcd_selected-yres;*bpp g_p_lcd_selected-bpp; } 注意在lcd.h中声明一下get_lcd_params函数。  4最后往FrameBuffer中写入数据让LCD整个屏幕显示红色因为这情况最简单。 想让整个屏幕显示红色那么就要从FrameBuffer首地址开始一直填充红色对应的数据。 假设采用16BPP且5:6:5我们设置的就是如此红色对应的数据是多少呢 16BPP意味着一个像素数据占据16bit5:6:5表示R占据5bit即bit[15:11]、G占据6bit即bit[10:5]、B占据5bit即bit[4:0]。 当bit[15:11]全为1表示红色bit[10:5]全为0表示没有绿色成分bit[4:0]全为0表示没有蓝色成分。 所以全红色则为0b 11111 000000 00000 0x f800如下图所示 算出红色对应的数据之后就需要从FrameBuffer首地址开始填充一帧画面对应的数据 p (unsigned short *)fb_base;//注意一下这里的强制类型转换则p时表示移动2字节 for (y 0; y yres; y) //遍历每一行for (x 0; x xres; x)//遍历每一行中的每一个像素*p 0xf800; 注意一下“p (unsigned short *)fb_base;”其强制类型转换为(unsigned short *)类型则p1则表示移动2个字节刚好符合16BPP时每个像素在内存中占2个字节的情形。下面采用24BPP时由于24BPP实际对应4字节低3字节是有效数据位高1字节无效所以要强制类型转换为(unsigned init*)见8.2中的4中的代码。 8.2 编译烧写运行验证  1修改Makefile和main.c 完成上面工作之后我们修改Makefile文件如下好看一些而已 然后在main.c文件修改 int main(void) {led_init();//interrupt_init(); /* 初始化中断控制器 */key_eint_init(); /* 初始化按键, 设为中断源 *///timer_init();puts(\n\rg_A );printHex(g_A);puts(\n\r);//nor_flash_test();lcd_test();//----现在要测试lcd------------------return 0; } 最后得到待编译的代码文件夹见005_simple_test_bad_017_007文件夹。  2根据错误提示修改代码 1我们执行make然后根据错误提示来修改源码 其中某些错误容易解决缺少头文件等但下面错误花了我一点时间解决这两个unknown field表示结构体里面没有这两项内容 xjhubuntu:~/iot/embedded_basic/jz2440/armBareMachine/005_simple_test_bad_017_007$ make arm-linux-gcc -marcharmv4 -c -o lcd/lcd_4.3.o lcd/lcd_4.3.c lcd/lcd_4.3.c:7: error: unknown field pins_polarity specified in initializer lcd/lcd_4.3.c:15: error: unknown field time_sequence specified in initializer make: *** [lcd/lcd_4.3.o] Error 1 xjhubuntu:~/iot/embedded_basic/jz2440/armBareMachine/005_simple_test_bad_017_007$后来发现错误是因为在lcd_4_3.文件中把数据类型pins_polarity、time_sequence当做变量进行赋值了改回来即可 2修改完上面这个内容后编译又弹出问题解决呗 首先也是缺少头文件在lcd/lcd_controller.c文件开头添加头文件即可注意一下包含s3c2440_soc.h这个头文件时的写法这是因为它与lcd目录同级 #include lcd_controller.h #include lcd.h #include ../s3c2440_soc.h 3解决完上面错误后又会弹出关于lcd/s3c2440_lcd_controller.c文件的错误也是缺少头文件以及把enable错写成enalbe了、多添加了“|”符号。改过来就好。  4重新编译又弹出问题包括strcmp没有定义、除法错误等问题 A、s3c2440_lcd_controller.c文件中下面这部份代码用到了除法。为了简单起见这里先直接赋值为5后面再想办法解决这个除法的问题。这在5.1.12中曾经说过这个问题。 //int clkval (double)HCLK/plcdparams-time_seq.vclk/2-10.5;int clkval 5;int bppmode plcdparams-bpp 8 ? 0xb :\plcdparams-bpp 16 ? 0xc :\0xd; /* 0xd: 24,32bpp */LCDCON1 (clkval8) | (35) | (bppmode1) ; B、解决strcmp没有定义这个问题。打开string_utils.c文件与lcd文件夹同层目录中在里面实现一个strcmp函数即可可以直接复制u-boot中的代码比如下面代码就是直接拷贝过来的 /*** strcmp - Compare two strings* cs: One string* ct: Another string*/ int strcmp(const char * cs,const char * ct) {register signed char __res;while (1) {if ((__res *cs - *ct) ! 0 || !*cs)break;}return __res; } 3实验现象 重新编译可以通过烧写到开发板中运行。实验现象如预期所料整个屏幕全红见链接。 还可以这样玩因为上面8.14中已经说明采用16BPP时 全红色对应的数据是0b 11111 000000 00000 0x f800全绿色对应的数据是0b 00000 111111 00000 0x7e0全蓝色对应的数据是0b 00000 000000 11111 0x1f。 则我们可以让屏幕轮流显示全红、全绿、全蓝代码如下没啥特别意义单纯好看而已实验现象见链接。 if (bpp 16){for(;;){p (unsigned short *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p 0xf800;//红delay(100000);p (unsigned short *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p 0x07e0;//绿 delay(100000);p (unsigned short *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p 0x001f;//蓝delay(100000);}} 4补充说明 由2.31可知采用24BPP时每个像素使用24bit来表示但实际上每个像素在内存中会使用4个字节来表示从这角度来说24BPP和32BPP其实是一样的而低3字节即bit[23:0]才是RGB数据其中R占据8bit即bit[23:16]G也是占据8bit即bit[15:8]B占据8bit即bit[7:0]。 但问题是LCD控制器与LCD屏幕之间用于传输RGB数据的只有16根线采用24BPP时FrameBuffer中的数据是如何传送给LCD屏幕的可以这样理解采用24BPP时LCD控制器从FrameBuffer取得4字节数据后只有低3字节有效发出8条红色、8条绿色、8条蓝色数据但分别只用到其中的高5条红线、高6根绿线、高5根蓝线由原理图可以得知VD[23:19]表示红色数据VD[15:10]表示绿色数据VD[7:3]表示蓝色数据。 当采用24BPP时全红对应着数据0b xxxxxxxx 11111xxx 000000xx 00000xxx 。x表示1或0都行这里让x0时则全红对应着数据0x00f80000。同理全绿对应着0x0000fc00全蓝对应着0x000000f8。 这里插讲一下当采用8BPP时可以通过调色板转换为16BPP这样就可以在LCD上显示了。 接下来我们试一下使用24BPP是否起效果。 首先将lcd_4.3.c文件中.bpp的值改为32注意一定是改为32而非24原因见注释 #define LCD_FB_BASE 0x33c00000lcd_params lcd_4_3_params {//篇幅缘故这里省略部分代码.xres 480,.yres 272,//.bpp 16,.bpp 32, //修改这里由于24BPP和32BPP在内存中一个像素都是32位//所以这里要写为32而不能写为24//另外后面LCDSADDR2寄存器要通过/8计算FB的结束地址这里如果写24则不对了.fb_base LCD_FB_BASE, }; 然后在s3c2440_lcd_controller.c中修改如下 //修改点1其实不用修改 int bppmode plcdparams-bpp 8 ? 0xb :\plcdparams-bpp 16 ? 0xc :\0xd; /* 0xd: 24,32bpp *///修改点2LCDCON5寄存器处将24改为32 pixelplace plcdparams-bpp 32 ? (0) : \plcdparams-bpp 16 ? (1) : \(11); /* 8bpp */ 然后在lcd_test.c文件中添加下面代码在lcd_test函数开始位置记得定义unsigned int *p2; if (bpp 16) {for(;;){p (unsigned short *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p 0xf800;//红delay(100000);p (unsigned short *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p 0x07e0;//绿 delay(100000);p (unsigned short *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p 0x001f;//蓝delay(100000);} }//在lcd_test()开始位置记得定义 unsigned int *p2; if(bpp32) {for(;;){p2 (unsigned int *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)//*p 0xf800;//红*p2 0x00f80000;delay(100000);p2 (unsigned int *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)//*p 0x07e0;//绿*p2 0x0000fc00;delay(100000);p2 (unsigned int *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)//*p 0x001f;//蓝*p2 0x000000f8;delay(100000);} } 重新编译烧写运行发现实验结果符合预期。说明采用24BPP时也能起效。 九、LCD编程_画点、线、圆 9.1 实现画点  线条、圆、字母都是以点为基础的所以先实现画点。在3.1程序框架中画点是在framebuffer.c中实现的。该文件内容如下 #include lcd.h/*获得LCD参数*/ static unsigned int fb_base; static int xres, yres, bpp; void fb_get_lcd_params(void) {get_lcd_params(fb_base, xres, yres, bpp); }/*将32bpp其实是24bpp转换为16bpp*rgb是32位的位含义是0x00RRGGBB*/ unsigned short convert32bppto16bpp(unsigned int rgb) {int r (rgb 16) 0xff;int g (rgb 8) 0xff;int b rgb 0xff;/* rgb565 */r r 3;g g 2;b b 3;return ((r11) | (g5) | (b)); }/*功能实现画点*color是32bit的,位含义是0x00RRGGBB*/ void fb_put_pixel(int x, int y, unsigned int color) {unsigned char *pc; /* 8bpp */unsigned short *pw; /* 16bpp */unsigned int *pdw; /* 32bpp */unsigned int pixel_base fb_base (xres * bpp / 8) * y x * bpp / 8;switch (bpp){case 8:pc (unsigned char *) pixel_base;*pc color;break;case 16:pw (unsigned short *) pixel_base;*pw convert32bppto16bpp(color);break;case 32:pdw (unsigned int *) pixel_base;*pdw color;break;} } 下面是对这段代码的一些解释说明 1framebuff.c文件中的fb_get_lcd_params函数调用了lcd.c文件中的get_lcd_params函数见8.1节的3来获取LCD芯片的一些参数后续将根据这些参数进行一些设置。 为什么要将get_lcd_params函数封装成fb_get_lcd_params函数呢因为纯软件层的画线画圆写字把它们叫做“纯软件层的上级”与lcd.c这层LCD芯片特性层之间隔着framebuffer.c这层把它叫做“纯软件层的下级”。根据分层思想画线条画圆写字这层不能跨越framebuffer.c这层去访问lcd.c文件这层只能访问到framebuffer.c这层。 注意在framebuff.h中声明一下fb_get_lcd_params函数。 2如何显示某一个像素的颜色呢 假设这个像素的坐标是x,y我们需要找到这个像素对应着FrameBuffer中哪个地址然后在该地址写入这个像素颜色的数据即可。 1首先明确地址像素x,y的数据应该放在 fb_base (xres * bpp / 8) * y x * bpp / 8 地址处。 2然后明确在该地址填入什么样的数据 对于8PP每个像素在FrameBuffer中占据8位因此采用unsigned char类型对于16PP每个像素在FrameBuffer中占据16位因此采用unsigned short类型对于32PP每个像素在FrameBuffer中占据32位因此采用unsigned int类型。 在编程时我们一般将颜色的数据类型设置为int类型其位含义是0x00RRGGBB比如下面的color参数就是int类型。 void fb_put_pixel(int x, int y, unsigned int color) 如何将int类型的数据其位含义是0x00RRGGBB转换为符合要求的BPP数据并写入FrameBuffer中进行显示呢 如果需要转化为32BPP或者24BPP因为24BPP在内存中也是32位则大小刚好对应直接*pc color即可。 如果需要转化为8BPP由于8BPP需要通过调色板来实现后面再讲解这里先暂时设置为*pc color。 如果需要转为16BPP则使用convert32bppto16bpp函数将32BPPint类型数据其实可以看作是32BPP格式的数据转换为16BPP。 3关于convert32bppto16bpp函数的说明如下如何将RGB888转换为RGB565 上面提到表示颜色的int类型数据其位含义是0x00RRGGBB等同于32BPP格式的数据。 32BPP其实与24BPP差别不大都有RGB888R是bit[23:16]、G是bit[15:8]、B是bit[7:0]如下图所示 16BPP的RGB568是分别截取RGB888的bit[23:19]、bit[15:11]、bit[7:3]形成的对应着下面代码 /*函数功能将32bpp转换为16bpp将RGB888转换为RGB565*参数rgb是int类型32位的位含义是0x00RRGGBB*注意返回值是unsigned short类型的2个字节*/ unsigned short convert32bppto16bpp(unsigned int rgb) {//分别取出RGB888中的组分int r (rgb 16) 0xff;int g (rgb 8) 0xff;int b rgb 0xff;//形成RGB565r r 3;g g 2;b b 3;return ((r11) | (g5) | (b)); //返回值是unsigned short类型2字节 } 9.2 实现画线画圆 在完成画点函数之后我们可以继续实现画线画圆放在geometry.c文件中注意在其对应头文件中声明一下有哪些函数。 geometry.h文件如下 #ifndef _GEOMETRY_H #define _GEOMETRY_Hvoid draw_circle(int x, int y, int r, int color); void draw_line(int x1,int y1,int x2,int y2,int color);#endif /* _GEOMETRY_H */ geometry.c文件如下 #include framebuffer.h/** http://blog.csdn.net/p1126500468/article/details/50428613*///-------------画圆函数。参数圆心半径颜色---------- // 画1/8圆 然后其他7/8对称画 // ----------------X // |(0,0) 0 // | 7 1 // | 6 2 // | 5 3 // (Y)V 4 // // L x^2 y^2 - r^2 void draw_circle(int x, int y, int r, int color) { int a, b, num; a 0; b r; while(22 * b * b r * r) // 1/8圆即可 { fb_put_pixel(x a, y - b,color); // 0~1 fb_put_pixel(x - a, y - b,color); // 0~7 fb_put_pixel(x - a, y b,color); // 4~5 fb_put_pixel(x a, y b,color); // 4~3 fb_put_pixel(x b, y a,color); // 2~3 fb_put_pixel(x b, y - a,color); // 2~1 fb_put_pixel(x - b, y - a,color); // 6~7 fb_put_pixel(x - b, y a,color); // 6~5 a; num (a * a b * b) - r*r; if(num 0) { b--; a--; } } } //-----------画线。参数起始坐标终点坐标颜色-------- void draw_line(int x1,int y1,int x2,int y2,int color) { int dx,dy,e; dxx2-x1; dyy2-y1; if(dx0) { if(dy 0) // dy0 { if(dxdy) // 1/8 octant { edy-dx/2; while(x1x2) { fb_put_pixel(x1,y1,color); if(e0){y11;e-dx;} x11; edy; } } else // 2/8 octant { edx-dy/2; while(y1y2) { fb_put_pixel(x1,y1,color); if(e0){x11;e-dy;} y11; edx; } } } else // dy0 { dy-dy; // dyabs(dy) if(dxdy) // 8/8 octant { edy-dx/2; while(x1x2) { fb_put_pixel(x1,y1,color); if(e0){y1-1;e-dx;} x11; edy; } } else // 7/8 octant { edx-dy/2; while(y1y2) { fb_put_pixel(x1,y1,color); if(e0){x11;e-dy;} y1-1; edx; } } } } else //dx0 { dx-dx; //dxabs(dx) if(dy 0) // dy0 { if(dxdy) // 4/8 octant { edy-dx/2; while(x1x2) { fb_put_pixel(x1,y1,color); if(e0){y11;e-dx;} x1-1; edy; } } else // 3/8 octant { edx-dy/2; while(y1y2) { fb_put_pixel(x1,y1,color); if(e0){x1-1;e-dy;} y11; edx; } } } else // dy0 { dy-dy; // dyabs(dy) if(dxdy) // 5/8 octant { edy-dx/2; while(x1x2) { fb_put_pixel(x1,y1,color); if(e0){y1-1;e-dx;} x1-1; edy; } } else // 6/8 octant { edx-dy/2; while(y1y2) { fb_put_pixel(x1,y1,color); if(e0){x1-1;e-dy;} y1-1; edx; } } } } } 9.3 测试与验证 修改一下测试程序lcd_test.c文件 #include geometry.hvoid lcd_test(void) {unsigned int fb_base;int xres, yres, bpp;int x, y;unsigned short *p;unsigned int *p2;/* 初始化LCD */lcd_init();/* 使能LCD */lcd_enable();//这段代码有些怪异……不是使用一个应该就可以了比如使用fb_这个验证确实需要写两个/* 获得LCD的参数: fb_base, xres, yres, bpp */get_lcd_params(fb_base, xres, yres, bpp);fb_get_lcd_params();/* 往framebuffer中写数据 */if (bpp 16){/* 让LCD输出整屏的红色 *//* 565: 0xf800 */p (unsigned short *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p 0xf800;/* green */p (unsigned short *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p 0x7e0;/* blue */p (unsigned short *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p 0x1f;/* black */p (unsigned short *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p 0;}else if (bpp 32){/* 让LCD输出整屏的红色 *//* 0xRRGGBB */p2 (unsigned int *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p2 0xff0000;/* green */p2 (unsigned int *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p2 0x00ff00;/* blue */p2 (unsigned int *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p2 0x0000ff;/* black */p2 (unsigned int *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p2 0;}delay(1000000);/* 画线 */draw_line(0, 0, xres - 1, 0, 0xff0000);//上边线draw_line(xres - 1, 0, xres - 1, yres - 1, 0xffff00);//右边线draw_line(0, yres - 1, xres - 1, yres - 1, 0xff00aa);//下边线draw_line(0, 0, 0, yres - 1, 0xff00ef);//左边线draw_line(0, 0, xres - 1, yres - 1, 0xff4500);//左上到右下draw_line(xres - 1, 0, 0, yres - 1, 0xff0780);//右上到左下delay(1000000);/* 画圆 */draw_circle(xres/2, yres/2, yres/4, 0xff00);//在屏幕中心画一个半径为yres/4的圆 } 从代码可知我们想要下图这样的显示效果六条线一个圆: 以上修改之后得到“007_dot_line_circle_017_008”文件夹将之编译烧写与运行发现实验现象如下所示可见符合预期 注意一下上面lcd_test()函数调用了两次获取LCD参数的函数 //这段代码有些怪异……不是使用一个应该就可以了比如使用fb_这个验证确实需要写两个/* 获得LCD的参数: fb_base, xres, yres, bpp */get_lcd_params(fb_base, xres, yres, bpp);fb_get_lcd_params(); 我试过只用第一个不用第二个发现可以显示红绿蓝全屏但不能显示六线一圆只使用第二个时现象很怪异没有六线一圆但也不是红绿蓝全屏只有使用两个时才符合预期。有空解决一下。 十、LCD编程_显示字母 10.1 字母显示原理  本节实现如何在LCD上显示字母。 字母也是由像素点构成的一个个像素点组成的点阵在宏观上就表现为字母。那某个字母对应怎样的点阵呢我们参考Linux内核源码中在源码中搜索“font”选择打开font_8x16.c文件由文件名字可知一个字母用8*16个像素点来表示发现里面是一个数组。 以字符A为例它对应的点阵如下图所示。这个图含义8位的二进制数据中1表示某个像素点有颜色0则表示没有颜色。另外由图可知一个字母对应着8*16个像素点。 10.2 实现字母显示 接下来实现字母的显示代码放在lcd/font.c文件中。 首先要把上面的font_8x16.c文件复制到font.c同级目录中然后修改一下font_8x16.c文件删掉 #include linux/font.h删除数组的static修饰符否则别的文件无法访问它。  font.c文件的内容如下另外要编写对应的头文件font.h extern const unsigned char fontdata_8x16[]; /* 获得LCD参数 */ static unsigned int fb_base; static int xres, yres, bpp;void font_init(void) {get_lcd_params(fb_base, xres, yres, bpp); }/* 根据字母的点阵在LCD上描画文字 */ void fb_print_char(int x, int y, char c, unsigned int color) {int i, j;/* 根据c的ascii码在fontdata_8x16中得到点阵数据 */unsigned char *dots fontdata_8x16[c * 16];unsigned char data;int bit;/* 根据点阵来设置对应象素的颜色 */for (j y; j y16; j){data *dots;bit 7;for (i x; i x8; i){/* 根据点阵的某位决定是否描颜色 */if (data (1bit))fb_put_pixel(i, j, color);bit--;}} }/* abc\n\r123 */ void fb_print_string(int x, int y, char* str, unsigned int color) {int i 0, j;while (str[i]){if (str[i] \n)y y16;else if (str[i] \r)x 0;else{fb_print_char(x, y, str[i], color);x x8;if (x xres) /* 换行 */{x 0;y y16;}}i;} } 下面是对这段代码的一些解析说明 1下面这段代码是因为每个字符在点阵数组中占据16字节。 /* 根据c的ascii码在fontdata_8x16中得到点阵数据 */unsigned char *dots fontdata_8x16[c * 16]; 2如何显示一个字符呢由下图可知一个字符对应8*16个像素每行8个像素共16行。 我们可以根据font_8x16.c提供的点阵数据一行行地描所以对应着以下代码 void fb_print_char(int x, int y, char c, unsigned int color) {int i, j;/* 根据c的ascii码在fontdata_8x16中得到点阵数据 */unsigned char *dots fontdata_8x16[c * 16];unsigned char data;int bit;/* 根据点阵来设置对应象素的颜色 */for (j y; j y16; j)//一次循环描一行共有16行{data *dots;bit 7;for (i x; i x8; i)//一次循环描一个点每行有8个点{/* 根据点阵的某位决定是否描颜色 */if (data (1bit))fb_put_pixel(i, j, color);bit--;}} } 3接着基于上面的 fb_print_char 函数实现了一个打印字符串的函数如下所示。 /*在xy开始处打印字符串str字符串的颜色是color* 例如输出“abc\n\r123”*/ void fb_print_string(int x, int y, char* str, unsigned int color) {int i 0, j;while (str[i]){if (str[i] \n)//如果遇到换行y y16; //每行字符的高度是16像素点else if (str[i] \r)//如果遇到回车x 0;//回到某行的0像素点else//如果是其他字符打印呗{fb_print_char(x, y, str[i], color);//打印一个字符x x8;if (x xres) /* 换行 */ //感觉这个换行的判断有些问题{ x 0;y y16;}}i;} } 我觉得下面代码有问题 else//如果是其他字符打印呗{fb_print_char(x, y, str[i], color);//打印一个字符x x8;if (x xres) /* 换行 */ //感觉这个换行的判断有些问题{ x 0;y y16;}} 应该改为 else//如果是其他字符打印呗{if((xres-x)8)//先判断特殊情况选取的x具距离边界不够8个像素无法显示1个字符{ //此时就应该直接换行x0;yy16;}/* else if( (xres-x)8 (xres-x)16 ){fb_print_char(x, y, str[i], color);//打印一个字符x0;yy16;}*/else{fb_print_char(x, y, str[i], color);//打印一个字符xx8;}} 10.3 测试与验证 首先修改一下lcd_test.c文件如下所示 1包含头文件font.h 2调用font_init函数来获取LCD参数 3调用fb_print_string函数输出某个字符串比如“XJH is very handsome\n\rXJH has no money!\n\rWhat a pity!”。 然后修改一下Makefile文件添加下面语句 objs lcd/font.o  objs lcd/font_8x16.o 实验现象如下可见符合预期 至于如何显示汉字或者显示更加复杂的矢量字符在第三期项目数码相框中有更加详细的介绍使用应用程序来显示字符但原理和本文裸板显示字符是一样的。 十一、LCD编程_解决除法问题 11.1 问题背景 在8.224A中提到因为缺失某些除法函数__floatsidf、__divdf3、__subdf3、、__fixdfsi编译时出错如下所示 lcd/s3c2440_lcd_controller.o(.text0xb4): In function s3c2440_lcd_controller_init: : undefined reference to __floatsidf lcd/s3c2440_lcd_controller.o(.text0xd0): In function s3c2440_lcd_controller_init: : undefined reference to __divdf3 lcd/s3c2440_lcd_controller.o(.text0xec): In function s3c2440_lcd_controller_init: : undefined reference to __divdf3 lcd/s3c2440_lcd_controller.o(.text0x108): In function s3c2440_lcd_controller_init: : undefined reference to __subdf3 lcd/s3c2440_lcd_controller.o(.text0x124): In function s3c2440_lcd_controller_init: : undefined reference to __adddf3 lcd/s3c2440_lcd_controller.o(.text0x138): In function s3c2440_lcd_controller_init: : undefined reference to __fixdfsi make: *** [all] Error 1 下面这段代码位于s3c2440_lcd_controller.c文件中为了解决编译错误我们注释掉除法计算的过程直接给变量赋一个定值5。 //int clkval (double)HCLK/plcdparams-time_seq.vclk/2-10.5;int clkval 5;int bppmode plcdparams-bpp 8 ? 0xb :\plcdparams-bpp 16 ? 0xc :\0xd; /* 0xd: 24,32bpp */LCDCON1 (clkval8) | (35) | (bppmode1) ; 但采取这样的解决方法有个弊端即如果换另一款LCD则需要修改这个定值。所以现在我们在第十节代码的基础上将上面这段代码的注释打开并删除给变量clkval赋定值5的语句然后实现错误提示中所涉及的除法函数。 实际上lib1funcs.S文件中是有除法的该文件以及string_utils.c文件是在哪个章节引入的但功能不够强大。 11.2 解决问题  这里提供几个解决问题的思路 可以参考集裸机程序大成者u-boot的源码如果它里面有相关的实现。可以在内核源码中查找它里面一般会实现这些除法函数。可以在库函数中查找编译器一般会自带很多基本的库文件比如数学库libm.a。可以参考网上别人的实现如果有的话。 1在u-boot中查找 比如对于__floatsidf函数我们在SI中打开u-boot的源码查找是否有该函数的实现代码没有接着搜索__divdf3也没有接着搜索__subdf3也没有……居然一个也没有 2在内核源码中寻找 在内核源码中查找看看。比如在linux-2.6.22.6版本的代码中查找只发现下面这段被注释掉的代码没什么参考价值 3链接时添加库文件 数学库中肯定实现了这些除法函数但库文件一般以文件形式发布比如.a或者.so文件我们无法看到这些除法函数的源码。现在我们想找到这些除法函数的源码因此在网上百度与必应搜寻“ undefined reference to __floatsidf ”但是找不到靠谱的解决方法。 那就在链接时添加这些除法函数对应的库吧。流程如下 通过“arm-linux-gcc -version”查看当前使用的交叉编译工具链查看编译器的版本。通过“echo $PATH”查看当前使用的交叉编译工具链所在的路径查看编译器在哪里。在交叉编译工具链所在路径搜索相关函数例如“ grep __floatsidf ./ -nr ”获知该函数在哪个库文件中实现。该函数可能在很多库文件中都有实现我们把其中的静态库文件即.a后缀的文件拷贝到代码文件夹中。修改Makefile即在arm-linux-ld命令中依次加入每个静态库文件直至编译成功。然后在代码文件夹中保留能使编译成功的库文件删掉其他拷贝过来的库文件为了节省存储空间。 比如我们要明确__floatsidf函数在哪个库文件中实现 xjhubuntu:/$ echo $PATH /opt/hisi-linux/x86-arm/arm-hisiv300-linux/target/bin:/usr/local/arm/gcc-3.4.5-glibc-2.3.6/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games xjhubuntu:/$ arm-linux-gcc --version arm-linux-gcc (GCC) 3.4.5 Copyright (C) 2004 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.xjhubuntu:/$ cd /usr/local/arm/gcc-3.4.5-glibc-2.3.6 xjhubuntu:/usr/local/arm/gcc-3.4.5-glibc-2.3.6$ grep __floatsidf ./ -nr Binary file ./include/c/3.4.5/arm-linux/bits/stdc.h.gch/O0g matches Binary file ./include/c/3.4.5/arm-linux/bits/stdc.h.gch/O2g matches Binary file ./tmp/arm-linux-hello2-static matches ./info/gccint.info:556: -- Runtime Function: double __floatsidf (int I) ./info/gccint.info:24593:* __floatsidf: Soft float library routines. Binary file ./lib/gcc/arm-linux/3.4.5/libgcc.a matches Binary file ./arm-linux/bin/localedef matches Binary file ./arm-linux/bin/sprof matches Binary file ./arm-linux/lib/libgcc_s.so.1 matches Binary file ./arm-linux/lib/libm.a matches Binary file ./arm-linux/lib/libc.a matches Binary file ./arm-linux/lib/libiberty.a matches Binary file ./arm-linux/lib/libc-2.3.6.so matches Binary file ./arm-linux/lib/libm-2.3.6.so matches xjhubuntu:/usr/local/arm/gcc-3.4.5-glibc-2.3.6$ 可见在libm-2.3.6.so、libc-2.3.6.so、libiberty.a、libc.a、libm.a、libgcc_s.so.1、libgcc.a等文件中都有__floatsidf函数的实现。我们将静态库文件因为我们使用静态链接libiberty.a、libc.a、libm.a、libgcc.a文件拷贝到代码文件夹中。但具体哪个能用需要一一尝试。 现在试一下将libc.a文件链接进去看能否编译成功。将Makefile文件修改如下在arm-linux-ld命令中加入参数libc.a执行编译指令make时还是报同样的错误可知libc.a不是合适的库文件。 //篇幅缘故省略其他 all: $(objs)arm-linux-ld -T sdram.lds $^ libc.a -o sdram.elfarm-linux-objcopy -O binary -S sdram.elf sdram.binarm-linux-objdump -D sdram.elf sdram.dis 经过一一尝试最后发现链接时只有添加libgcc.a这个库文件才可以使得编译通过。添加libm.a居然不行看来这些除法函数的实现是放在libgcc.a库文件中而不是放在libm.a数学库文件中。 注意如果你更换了交叉编译工具链需要自己去交叉编译工具链目录下寻找对应的libgcc.a有可能有多个libgcc.a需要逐个尝试。 本节的问题似乎在博文1、博文2中有相关的描述。 十二、LCD编程_调用调色板 12.1 调色板原理  前面写的程序都是采用16BPP或24BPP即32BPP假如我们要使用8PP则需要使用调色板。 在2.4节中我们曾经讲过调色板的工作原理采用8BPP时某个像素的数据此时称为索引在FrameBuffer中占据8位则调色板中有2^8256项颜色数据每项颜色数据占16位。LCD控制器把FrameBuffer中的8位数据当做索引在调色板中找到它对应的16位数据然后将这16位数据发给电子枪。 调色板是一块内存有自己的地址和格式。在S3C2440数据手册中搜索“PALETTE”在P416有以下内容由此可知调色板的起始地址0x4D00_0400这个地址隶属于LCD控制器一共有256项每项占据4个字节但只用到最低2个字节。 12.2 调用调色板  在硬件上我们要初始化这个调色板才能通过索引得到颜色。 根据第三节的软件框架调色板初始化函数应该放在s3c2440_lcd_controller.c文件里面。 1、我们先在lcd_controller.h文件中修改lcd_controller结构体的定义添加一个函数指针成员 #ifndef _LCD_CONTROLLER_H #define _LCD_CONTROLLER_H#include lcd.htypedef struct lcd_controller {char *name;void (*init)(p_lcd_params plcdparams);void (*enable)(void);void (*disable)(void);void (*init_palette)(void);//添加这个 }lcd_controller, *p_lcd_controller;#endif /* _LCD_CONTROLLER_H */ 2然后在s3c2440_lcd_controller.c文件中给s3c2440_lcd_controller结构体的函数指针成员init_palette赋初值 struct lcd_controller s3c2440_lcd_controller {.name s3c2440,.init s3c2440_lcd_controller_init,.enable s3c2440_lcd_controller_enalbe,.disable s3c2440_lcd_controller_disable,.init_palette s3c2440_lcd_controller_init_palette, }; 接下来我们要实现s3c2440_lcd_controller_init_palette函数即调色板初始化函数 /* 设置调色板之前, 先关闭lcd_controller */ void s3c2440_lcd_controller_init_palette(void) {volatile unsigned int *palette_base (volatile unsigned int *)0x4D000400;int i;// 取出LCDCON1[0]下面的if判断它是否为1是否已经打开LCD控制器打开为1关闭为0int bit LCDCON1 (10);/* LCDCON1BIT 0 : 设置LCD控制器是否输出信号 */if (bit)LCDCON1 ~(10);//如果打开了就先关闭LCD控制器//填充调色板数据数组调色板一共有256种颜色它们的颜色数据是什么这里来定义for (i 0; i 256; i){/* 低16位 : rgb565 */ *palette_base i;}if (bit)//这里难道不是if(!bit)吗LCDCON1 | (10); } 下面是对这段代码的一些说明 1设置调色板前先判断LCD控制器是否打开如果打开了就先关闭且设置完成后再打开。 2在网上没有找到调色板数据对应的数组这里作为实验就随便设置了比如我们让调色板数据等于ii0~255。这样的设置会让调色板的颜色整体偏蓝因为红色成分为05bit全为0绿色成分也很少只用到了3bit而蓝色用到了5bit即相当于R0G3B5。 3修改lcd_4.3.c文件将BPP改为8。 4再修改lcd_test.c文件加入BPP8的情形让屏幕显示某种颜色 if (bpp 8){/* bpp: palette[12] */p0 (unsigned char *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p0 12;/* palette[47] */p0 (unsigned char *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p0 47;/* palette[88] */p0 (unsigned char *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p0 88;/* palette[0] */p0 (unsigned char *)fb_base;for (x 0; x xres; x)for (y 0; y yres; y)*p0 0;} 5再修改Icd_controller.c文件中lcd_controller_init函数在里面加入调色板初始化函数 /* 向上: 接收不同LCD的参数* 向下: 使用这些参数设置对应的LCD控制器*/int lcd_controller_init(p_lcd_params plcdparams) {/* 调用所选择的LCD控制器的初始化函数 */if (g_p_lcd_controller_selected){g_p_lcd_controller_selected-init(plcdparams);g_p_lcd_controller_selected-init_palette();//加入这行代码return 0;}return -1; } 6在lcd_test.c调用的画线画圆、显示字母的函数里修改下颜色其实只有低两位有效因为调色板映射范围是0-255 /* 画线 */draw_line(0, 0, xres - 1, 0, 0x23ff77);draw_line(xres - 1, 0, xres - 1, yres - 1, 0xffff);draw_line(0, yres - 1, xres - 1, yres - 1, 0xff00aa);draw_line(0, 0, 0, yres - 1, 0xff00ef);draw_line(0, 0, xres - 1, yres - 1, 0xff45);draw_line(xres - 1, 0, 0, yres - 1, 0xff0780);delay(1000000);/* 画圆 */draw_circle(xres/2, yres/2, yres/4, 0xff);/* 输出文字 */fb_print_string(10, 10, www.100ask.net\n\r100ask.taobao.com, 0xff); 12.3 测试与验证 编译通过实验现象符合预期见链接。 十三、作业预留 1、如果有MINI2440、TQ2440或是带3.5寸LCD的JZ2440添加一个lcd_xxx.c文件构造lcd_params结构体体验一下结构化编程的优点可以很轻松支持其他LCD。 2、找到汉字库点阵在LCD上显示汉字。这需要你善用百度找到汉字库也许要阅读别人的代码了解汉字库点阵的存储方式。这是一个综合能力的体现。
http://www.dnsts.com.cn/news/58000.html

相关文章:

  • 成都建设网站公司简介怎样才能加入网络销售平台
  • 凡科建站登录个人网站设计与实现结论
  • 定制网站制作平台建设鲜花网站前的市场分析
  • 火星免费建网站视频剪辑软件app
  • 网站关键词字数关键词挖掘工具站
  • 广西省建设厅网站织梦网站错位
  • 广州网站建设如何汉化wordpress插件
  • 佛山市专业的网站设计wordpress 左导航
  • 贵阳网站设计企业thinkphp旅游网站源码
  • 有网络网站打不开怎么回事一流小说网站模板
  • 专业网站设计推荐自有品牌如何推广
  • 教学网站开发应指导方案泊头网站制作
  • 自己做网站处理图片用什么软件南宁推广公司
  • 通辽网站开发招聘专业的建设网站
  • 深圳城乡和建设局网站wordpress禁用文章定时发布
  • 百度收录网站与手机版网店美工主要负责什么工作
  • 云畅网站建设后台wordpress防止爬虫
  • 网站数据库是什么wordpress多说加载慢
  • 沧州网站建设王宝祥毛衣品 东莞网站建设
  • 怎么自己用手机做网站wordpress汉化.po
  • 我做推广找不到我的网站做电影网站用什么源码
  • 网站页面链接怎么做二维码图片生成器在线制作
  • 大观网站建设做网站用的服务器
  • 加快建设企业门户网站建长沙百度提升排名
  • 天津网站优化多少钱wordpress 招聘网站模板
  • 查公司信息的国家网站内容营销策略有哪些
  • 网站内文章外链如何做图片在线制作软件
  • 奢侈品网站 方案昆明网站建设猫咪科技
  • 网站建设与推广协议书响应网站
  • 微信学校网站模板一级消防工程师考试内容