一 LCD屏幕
本次学习使用的LCD屏是一块 800*480 分辨率的全彩屏幕。
分辨率的意思是:每行有800个像素点,一共有480行。
全彩的意思是:像素点显示的颜色是由RGB红绿蓝三种单颜色组成,其次还有阿尔法粒子透明度A。也就是ARGB。
LCD显示屏在工作中需要显卡,显卡中要有显存。而本次学习的开发板没有独立显卡,所以需要分配虚拟显存。而每个像素点在显存中占据4个字节的空间,也就是A、R、G、B各占一个字节。
二 操作显示屏
1 直接操作
我们仿照对文件的读写操作方法,来直接读写LCD显示屏。
对文件的操作可翻阅: 文件IO
首先,理一下思路,步骤是:
① 打开LCD对应的设备文件,利用open函数,权限是可读写,返回入口指针;
② 利用write函数往打开后的入口写入颜色数据;
③ 关闭设备文件,close函数。
这样做的依据与原理是什么呢?
如图,这样做的原理是编写程序直接访问LCD的设备文件,LCD属于字符设备,通过给设备文件写入数据,然后设备文件的驱动会一直被系统所调用,不断地来读取设备文件里面的数据,从而将其转化为实际的效果,也就是LCD屏幕像素点的显示效果。像图中,我们为第三个像素点写入0xff0000的数据,它将显示为红色。一个像素点的数值刚好为一个int型,也就是四个字节。
我们创建一个lcd.c文件,编写代码来实现这个原理。
#include <stdio.h>
/* open */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* write close */
#include <unistd.h>
int main()
{
int colorbuf[800*480] = {0};
int i;
//1 打开lcd,权限为读写
int lcd_fd = open("/dev/fb0", O_RDWR);
if(lcd_fd == -1)
{
perror("打开lcd错误,原因是");
return -1;
}
// 准备颜色数据
for(i=0; i<800*480; i++)
{
colorbuf[i] = 0xFF0000;
}
//2 写颜色数据
write(lcd_fd, colorbuf, 800*480*4);
//3 关闭lcd
close(lcd_fd);
}
我们先编译然后运行一下程序。可以看到整个屏幕被慢慢覆盖为红色。开始有一些小点没有被覆盖,但最终的效果是覆盖整个屏幕的。
这里解释一下代码,里面有一些地方可能会难以理解。
① int lcd_fd = open("/dev/fb0", O_RDWR);
open打开的就是LCD屏幕的设备文件,它的路径是在 /dev 根目录下设备目录中,它的名字为 fb0。需要给打开权限为只写或者可读写。然后存储返回的入口地址。
② for(i=0; i< 800*480; i++) { colorbuf[i] = 0xFF0000; }
这个 colorbuf 数组的类型为 int 型,数组大小为 800x480,这个数组代表整个屏幕所有的像素点,共有 800x480 个,每个像素点都是 int 型,这个在上面已经解释过了。用 for 循环遍历每个像素点,为每个像素点涂上红色。每个像素点的地址都是从左往右递增的,所以当第一行 800 个像素点赋值之后,下一个地址会指向第二行首个像素点,有点类似于自动换行。
③ write(lcd_fd, colorbuf, 800 * 480 * 4);
这可能是最难理解的地方了,难点在于第三个参数。其实这也不难理解。第一个参数是被写入数据的入口地址,第二个参数是写入的数据内容的首地址,第三个参数是所写内容的长度。由于 colorbuf 是 int 型,共有 800x480 个数据,而 int 型为 4 个字节,那么总的字节数就是 800x480x4 个。如果还是难以理解,那么就将程序改为strlen,也是一样的效果。
write(lcd_fd, colorbuf, strlen(colorbuf));
2 内存映射
直接操作的方法能够实现我们所要的显示效果。但是当运行程序时,可以观察到,程序在终端中很快就被运行完毕,可在LCD显示屏中,却出现类似卡顿的效果,像素点不是一下子铺满整个屏幕,而是缓慢地,一卡一卡地慢慢覆盖屏幕。这是为什么呢?
这是因为被程序打开的LCD设备文件处于系统的内核级,我们直接对内核级里面的文件进行修改,进行数据的写入,这些数据直接被驱动捕获,然后显示成图像被我们所看到。如果写入数据这一整个过程没有结束,我们观察到的图像就是一卡一卡,慢慢地覆盖屏幕。也就是说,驱动一直在跑,而数据写入跟不上,从而影响了整体的运行效率,最终造成了图像显示的卡顿。那么,如何解决这个问题呢?
解决这个问题的方法就是先将图像的颜色数据准备好,然后直接放入LCD设备文件。直接一整张图放入,而不是慢慢地写入。可这样的操作是不被允许的,因为LCD设备文件是处于系统的内核级,我们没有这样操作的权限。那如何办呢?只能采用内存映射的方法。
内存映射,就是将内核级的文件入口地址与我们编程使用的用户级内存空间进行映射。我们开辟一块内存空间,然后这块内存空间与内核级的文件捆绑,如果我们对这块内存空间进行操作,也就相当于对设备文件进行操作。
这样,当我们对映射的内存空间直接一次性放入图像颜色信息时,数据也会同时出现在内核级的文件中。那么,驱动读取到颜色数据就是流畅的,而不是一顿、一顿地读取。最终的结果就是图像流畅地进行显示。
我们创建一个lcd_map.c文件,编写代码来实现这个原理。
#include <stdio.h>
/* open */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* write close */
#include <unistd.h>
/* memcpy */
#include <string.h>
/* mmap */
#include <sys/mman.h>
int main()
{
int colorbuf[800*480] = {0};
int i;
//1 打开lcd
int lcd_fd = open("/dev/fb0", O_RDWR);
if(lcd_fd == -1)
{
perror("open lcd error");
return -1;
}
//映射,返回映射空间的首地址
int *lcd_memory = mmap(NULL, //映射空间的首地址, 如果写NULL 表示由系统自动分配空间
800*480*4, //要映射的空间的大小
PROT_READ|PROT_WRITE, //权限可读可写
MAP_SHARED, //映射空间的作用为共享内存
lcd_fd, //有效的文件描述符(入口)
0); //偏移量
// 准备颜色数据
for(i=0; i<800*480; i++)
{
colorbuf[i] = 0xFF0000;
}
//2 写颜色数据
//使用memcpy函数把一个空间的数据复制到另一个空间
//直接将数据拷贝放入,避免慢慢写入造成的图像卡顿
memcpy( lcd_memory, colorbuf, 800*480*4);
//释放内存空间
munmap(lcd_memory, 800*480*4);
//3 关闭lcd
close(lcd_fd);
}
我们对代码进行编译然后运行,可以看到屏幕瞬间就被点亮为红色。而不会出现卡顿的现象。
这里就不对代码进行解释。
三 作业
1、在LCD屏幕上,从上到下显示红橙黄绿青蓝紫七种颜色,实现彩虹屏幕效果。
2、封装LCD屏的显示函数,实现从左到右彩虹屏幕效果。
3、实现进度条滚动效果。
作业1代码:
/************ 作业1 *************/
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
int main()
{
int lcd_fd = open("/dev/fb0",O_RDWR);
int color_buf[800 * 480]={0};
//红橙黄绿青蓝紫
int color_caihong[7]={0xFF0000,0xFF6100,0xFFFF00,0x00FF00,0x00FFFF,0x0000FF,0xA020F0};
int i,j;
int* lcd_memory = mmap( NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd_fd,0);
//前6个区域,高为70
for(i=0;i<6;i++)
{
for(j=i*800*70 ; j<800*70*(i+1);j++)
color_buf[j] = color_caihong[i];
}
//第7个区域,高为60
for(i=800*70*6;i<800*480;i++)
{
color_buf[i] = color_caihong[6];
}
//写入颜色
memcpy(lcd_memory,color_buf,800*480*4);
munmap(lcd_memory,800*480*4);
close(lcd_fd);
return 0;
}
作业1效果:
作业2代码:
/************ 作业2 *************/
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
/**************************************
* 函数功能:指定坐标显示任意宽高任意颜色的矩形
* 函 数 名:showColor
* 参 数:x :起点x坐标
* y :起点y坐标
* width :宽度
* height:高度
* color :颜色
* 返 回 值:成功返回 0,失败返回 -1
* 说 明:按行从上到下扫描
*****************************************/
int showColor(int x, int y, int width, int height, int color)
{
int i,j,x_end,y_end;
int lcd_fd = open("/dev/fb0", O_RDWR);
int* lcd_memory = mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd_fd,0);
if(((x_end = x+width) > 800) || ((y_end = y+height) > 480))
{
printf("超过宽度或者高度限制\n");
return -1;
}
for(i=y; i<y+height; i++) //扫描行
{
for(j=x; j<x+width; j++) //扫描列
{
*(lcd_memory+i*800+j) = color;
}
}
munmap(lcd_memory,800*480*4);
close(lcd_fd);
return 0;
}
int main()
{
showColor(0,0,114,479,0xff0000);
showColor(114,0,114,479,0xff6100);
showColor(228,0,114,479,0xffff00);
showColor(342,0,114,479,0x00ff00);
showColor(456,0,114,479,0x00ffff);
showColor(570,0,114,479,0x0000ff);
showColor(684,0,114,479,0xa020f0);
return 0;
}
作业2效果:
作业3代码:
#include "stdio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
/**************************************
* 函数功能:清除屏幕内容
* 函 数 名:clearLCD
* 说 明:屏幕变黑
*****************************************/
void clearLCD()
{
int lcd_fd = open("/dev/fb0", O_RDWR);
int* lcd_memory = mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd_fd,0);
memset(lcd_memory,0,800*480*4);//将lcd_memory起始的800*480*4的空间设置为0
munmap(lcd_memory,800*480*4);
close(lcd_fd);
}
/**************************************
* 函数功能:指定坐标显示进度条
* 函 数 名:showBar
* 参 数:
* x :起点x坐标
* y :起点y坐标
* width :宽度
* height :高度
* color :颜色
* 说 明:按列从左到右扫描
*****************************************/
void showBar(int x,int y,int width,int height,int color)
{
int i,j;
int lcd_fd = open("/dev/fb0", O_RDWR);
int* lcd_memory = mmap(NULL,800*480*4,PROT_READ|PROT_WRITE,MAP_SHARED,lcd_fd,0);
for(j=x; j<x+width; j++)//扫描列
{
for(i=y; i<y+height; i++)//扫描行
{
lcd_memory[i*800+j] = color;
}
usleep(3000);//关键!!延时才能产生动态效果
}
munmap(lcd_memory,800*480*4);
close(lcd_fd);
}
int main()
{
clearLCD();
showBar(140,100,500,40,0xff0000);
return 0;
}