004LCD
1.LCD屏的原理与应用
1.1 基本概念
LCD (Liquid Crystal Display的简称)的中文称为液晶显示器,构造原理就是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置TFT(薄膜晶体管),上基板玻璃上设置彩色滤光片(CF),彩色滤光片是由红、绿、蓝三种颜色构成的滤片,有规律地制作在一块大玻璃基板上。

问题:LCD怎么成像
从底层硬件角度 LCD屏下的灯管的光会透过彩色滤光片(在中间)打到偏光片(上下两个)呈现颜色
背光层 → 下偏光片 → TFT层 → 液晶盒 → 彩色滤光片 → 上偏光片 → 人
1.2 结构分析
彩色滤光片是LCD液晶屏彩色化的关键部件,彩色滤光片通过滤光的方式可以产生RGB三原色,然后再将三原色以不同的强弱比例进行混合,从而呈现各种颜色,这样LCD才可以显示全彩色。

R 0-255、G 0-255、 B 0-255
每个颜色都是8bit - - - - ->1字节
RGB - - - - ->3字节
共组成256 * 256 * 256 种组合
2^32次方种
1.3 显示单位
屏幕上显示颜色的最小单位叫做像素,像素英文叫 pixel。位图(比如jpg、bmp等格式的常见图片)也是由一个个的像素点构成的,跟屏幕的像素点的概念一样。原理上讲,将一张位图显示到屏幕上,就是将图片上的像素点一个个复制到屏幕像素点上。

1.4 像素数量
图片或者屏幕的宽、高两个维度上的像素点数量被称为分辨率,分辨率决定了位图图像细节的精细程度。通常情况下,图像的分辨率越高则所包含的像素就越多,那么图像就越清晰,印刷的质量也就越好。当然,它也会增加文件占用的存储空间,也就是分辨率越高,则占用内存越大。

1.5 像素色深
每个像素点所占的bit位数被称为色深,色深的单位是bit。简单理解:色深就是指一个像素点的大小,一般有8位、16位、24位或32位,比如全志T113开发板的LCD屏的色深就是32位的,也就是一个像素点由4字节组成。分别是ARGB,其中A指的是透明度。RGB指的是三原色,每种颜色占1字节,也就是每种颜色的范围是0 ~ 255。

32位色深的LCD屏一般被称为真彩屏。色深决定了一个像素点所能表达的颜色的丰富程度,色深越大,色彩表现力越强。
使用时,需要将A设置为FF,完全不透明,才可以点亮
色深也称为位深度
1.6 驱动架构
LCD属于硬件设备,所以需要通过驱动程序才能控制,而在Linux系统中是通过 Framebuffer驱动子系统来控制LCD的。Frame是帧的意思,buffer是缓冲的意思,所以Framebuffer也被称为帧缓冲。Linux系统下面有多个子系统,其中 Framebuffer子系统就是Linux系统为显示设备提供的一个驱动程序接口,用于驱动显示设备。
Framebuffer驱动子系统会从内存中申请一块空间用于保存一帧图像的颜色数据,这块内存会被映射到进程的地址空间,这样进程就可以直接进行对帧缓冲进行读写操作,并且写操作可以立即刷新到屏幕上。
可以理解为Framebuffer驱动子系统把屏幕上的每个像素点映射成一段线性内存空间,应用程序直接修改这段内存空间的数据就相当于改变屏幕上某一像素点的颜色。

FrameBuffer帧缓冲写入时需要使用16进制
1.7 设备文件
在Linux系统中硬件设备主要分为两种:一种是字符设备(LCD、触摸屏、键盘......),另一种是块设备(硬盘、U盘......),这两种类型的硬件设备都需要由驱动程序(xxx.ko)控制,当驱动程序安装完成后会自动生成硬件的设备文件,用户才可以通过设备文件去访问硬件。
比如window系统的硬件驱动安装完成后会在设备管理器中生成设备文件,而Linux系统的硬件驱动安装完成后会自动在 */dev目录*下生成设备文件。


LCD屏在Linux系统下属于字符设备,需要通过Framebuffer驱动程序进行控制,而Framebuffer驱动安装之后会在 /dev目录下生成名字叫做fbn(n = 0 ~ 31)的设备文件,比如开发板的LCD屏的设备文件的名称就是*/dev/fb0*。
练习:德国国旗、画圆
设计程序,实现在开发板的LCD屏幕显示德国国旗,
拓展:在开发板的LCD上画一个圆

/******************************************************************
*
* 文件名称:show_color.c
* 文件作用:利用系统IO相关接口在LCD屏显示德国国旗(黑/红/黄三色)并观察运行效果!
* 文件作者:guokun
* 修改日期:
* 注意事项:linux系统下LCD设备文件的路径 /dev/fb0
*
* 版权声明:CopyRight (c) guokun603@gmail.com All Right Reserved
* ****************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
int main(int argc,const char *argv[])
{
//1、打开文件
int lcd_fb0=open("/dev/fb0",O_RDWR);
if(-1 == lcd_fb0){
perror("文件打开失败:");
return -1;
}
//点亮LCD
int blank_mode = 0; // 0=点亮,1=黑屏
ioctl(lcd_fb0, FBIOBLANK, &blank_mode);
//2、写入文件
//写入的缓冲区
unsigned short buf[1024*600]={0};
//写入数据,颜色
int i=0;
for(;i<1024*600;i++){
buf[i]=(unsigned short)0x0000;
}
for(;i<1024*900;i++){
buf[i]=(unsigned short)0xF800;
}
for(;i<1024*1200;i++){
buf[i]=(unsigned short)0xFFE0;
}
write(lcd_fb0,buf,sizeof(buf));
//3、关闭文件
close(lcd_fb0);
return 1;
}

根据这个修改参数
从第Y行开始,由于内存是线性的,X是偏移位置
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <linux/fb.h>
int main(int argc,char *argv[])
{
//打开文件,fb0
int lcd_fb0=open("/dev/fb0",O_RDWR);
if(-1==lcd_fb0){
perror("打开 /dev/fb0 失败");
return -1;
}
//设置缓冲区
unsigned int buf[1024*600]={0};
int x=0;
int y=0;
//先是第y行,再第x列
for(y=300-50;y<300+50;y++){
for(x=512-50;x<512+50;x++){
if ((x-512)*(x-512)+(y-300)*(y-300)<=50*50){
buf[1024*y+x]=0xFFFFFF00;
}
}
}
//写入
int write_len=write(lcd_fb0,buf,sizeof(buf));
if(write_len!=sizeof(buf)){
perror("写入屏幕失败:");
close(lcd_fb0);
return -1;
}
// 5、关闭文件
close(lcd_fb0);
return 0;
}
2、LCD屏幕硬件参数
如果后期开发过程中LCD屏的型号发生更换,那么请问上述代码是否可以正常运行?
回答:不可以,因为程序的可移植性较差,为了提高程序的可移植性,则需要根据LCD屏幕实际的硬件参数来申请内存。
2.1 硬件参数
在Linux系统下是利用Framebuffer子系统来驱动显示设备的,所以Framebuffer子系统会读取LCD屏的硬件信息并存储在内核空间中,关于LCD的硬件参数都是定义在*/usr/include/linux/fb.h*头文件中。


通过fb.h可以看到头文件中存在三个比较重要的结构体,分别是struct fb_fix_screeninfo以及struct fb_var_screeninfo以及struct fb_bitfield,除了这三个结构体之外,可以发现头文件中存在大量的宏定义,用户可以通过这些宏定义来指定读取LCD的部分信息。
struct fb_fix_screeninfo结构体
struct fb_fix_screeninfo结构体主要用于获取FrameBuffer的固定参数,用户无法在应用层修改这些参数,只能通过系统调用ioctl函数使用宏定义FBIOGET_FSCREENINFO去获取这些参数。


struct fb_var_screeninfo结构体
struct fb_var_screeninfo结构体主要用于获取和设置FrameBuffer的可变屏幕参数,包括分辨率、像素位深、像素格式等。这些信息可以通过ioctl函数使用宏定义FBIOGET_VSCREENINFO获取,也可以通过宏定义FBIOPUT_VSCREENINFO修改。




2.2 文件控制
Linux系统提供了一个ioctl(input/output control)函数,该函数是一个专用于设备输入输出操作的系统调用,该调用传入一个跟设备有关的请求码,系统调用的功能完全取决于请求码。


请求码是通过宏定义的方式写入到硬件的驱动程序中,当驱动安装成功后,用户就可以通过头文件中的请求码向内核中的驱动程序发出请求,然后内核通过请求码去访问硬件,再把访问结果返回给用户。
练习:获取参数
设计程序,利用ioctl函数获取LCD的硬件参数,把LCD屏幕的宽和高以及色深的位数输出到屏幕,下载程序到开发板并进行验证。 提示:必须包含该头文件 <linux/fb.h> !!!!
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <linux/fb.h>
int main(int argc,const char * argv[])
{
//打开文件,fb0
int lcd_fb0=open("/dev/fb0",O_RDWR);
if(-1==lcd_fb0){
perror("打开 /dev/fb0 失败");
return -1;
}
//使用ioctl获取LCD信息
//创建结构体接收获取的信息
struct fb_var_screeninfo var_info;
int a=ioctl(lcd_fb0,FBIOGET_VSCREENINFO,&var_info);
if(a<0){
perror("获取 LCD 参数失败");
close(lcd_fb0);
return -1;
}
//打印输出
printf("===== LCD 硬件参数 =====\n");
printf("屏幕宽度:%d 像素\n", var_info.xres);
printf("屏幕高度:%d 像素\n", var_info.yres);
printf("色深位数:%d 位(%d 字节/像素)\n",
var_info.bits_per_pixel,
var_info.bits_per_pixel / 8);
printf("虚拟分辨率:%dx%d\n", var_info.xres_virtual, var_info.yres_virtual);
printf("========================\n");
close(lcd_fb0);
return 0;
}
3.提高LCD显示效率
3.1 内存映射
可以发现通过系统IO直接操作LCD的时候LCD屏的部分像素点会显示“黑线”,过段时间或者重新再次运行程序之后“黑线”会慢慢消失。其实这种现象并不是由于硬件原因导致的,而是系统调用的过程中数据传输效率较低导致的。
写入过程:
int buf[800 * 480] -->*用户缓冲区*-->循环放入颜色分量-->调用write()写入LCD --> 把用户缓冲区的数据拷贝到*内核缓冲区*--->内核把缓冲区中的颜色分量依次写入*LCD的驱动程序*-->LCD看到显示效果。
由于write函数是系统调用,所以数据需要先从用户态进入到内核态,将用户态中的缓冲区数据拷贝到操作系统的内核缓冲区当中,此时已经完成了第一次数据拷贝,然后再将内核缓冲区中的数据再次拷贝到磁盘当中,此时完成了第二次拷贝。在这个过程当中我们进行了两次拷贝,数据传输的效率是比较低的。
为了提高数据的传输效率,那就需要减少拷贝次数,可以通过Linux系统的一个叫做mmap()的函数接口实现。mmap的全称是memory map,翻译为中文也就是内存映射,其实是一种内存映射文件的方法。
通过内存映射,用户可以将一个文件或者其它对象映射到进程的地址空间,从而实现磁盘的一段物理地址和进程的一段虚拟地址建立对应关系。
通过mmap()这个系统调用函数,用户可以让多个进程之间通过映射到同一个普通文件实现共享内存,普通文件被映射到进程地址空间之后,进程可以向访问普通内存一样对文件进行一系列操作,就不需要调用read/write函数,比如为LCD屏进行内存映射,用户把颜色数据写入到内存中,则LCD屏的像素点也会进行刷新。
mmap:内存映射
将用户空间和磁盘空间,建立了一个同步关联
当数据写入用户空间时,会同步写入磁盘空间,这样就减少了拷贝次数,实现高效

使用mmap可以减少一次拷贝,并且采用内存操作比read和write要简单一些,用户不需要在用户空间定义缓冲区来保存从内核缓冲区读上来的数据,从而节约了内存的消耗。

练习:mmap使用
设计程序,利用系统IO函数实现让开发板的LCD屏填充为德国国旗,要求使用内存映射的方式实现,程序设计完成后下载到开发板进行验证并观察效果。

/******************************************************************
*
* 文件名称:mmap.c
* 文件作用:使用mmap函数实现,LCD显示的显示效率
* 文件作者:guokun(修正32位色深适配)
* 修改日期:
* 注意事项:适配32位色深(4字节/像素),LCD设备路径 /dev/fb0
*
* 版权声明:CopyRight (c) guokun603@gmail.com All Right Reserved
* ****************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <sys/mman.h>
// 定义32位RGBA颜色(0xAA RR GG BB,AA=0xFF表示不透明)
#define COLOR_BLACK 0xFF000000 // 黑色(R=0,G=0,B=0)
#define COLOR_RED 0xFFFF0000 // 红色(R=255,G=0,B=0)
#define COLOR_YELLOW 0xFFFFFF00 // 黄色(R=255,G=255,B=0)
int main(int argc,const char *argv[])
{
// 1、打开LCD设备文件
int lcd_fb0 = open("/dev/fb0", O_RDWR);
if (-1 == lcd_fb0) {
perror("文件打开失败:");
return -1;
}
//2、对LCD进行内存映射 mmap函数
int *lcd_mp=(int *)mmap(NULL,
1024*600*4,
PROT_READ | PROT_WRITE,
MAP_SHARED,
lcd_fb0,
0
);
if (MAP_FAILED == lcd_mp){
printf("mmap for lcd is error!\n");
return -1;
}
for(int i=0;i<1024*600;i++){
lcd_mp[i]=COLOR_RED;
}
munmap(lcd_mp,1024*600*4);
close(lcd_fb0);
return 0;
}

浙公网安备 33010602011771号