1,显示屏和帧缓冲
我们开发板有一块7英寸的电容屏,用来显示图像。
该屏幕的分辨率是 480*800,也就是有480行,每行有800个像素点。
帧缓冲(Frame buffer):帧指的是一帧图像,缓冲是暂时存放的意思,连起来就是暂时存放一帧图像,相当于模拟了PC机中显卡的显存作用,在嵌入式设备中,一般是没有显卡这种专门处理图像的设备,所以就使用了帧缓冲这种机制用软件来模拟显存。Frame Buffer为显示设备提供了一个统一的接口(开发板连接不同的屏幕,操作方法是一样的,屏幕了底层硬件的差异),允许上传应用直接对帧缓冲区进行读写操作。
对于应用层而言,只需要在帧缓冲区写入颜色值,就能在屏幕上对应位置显示颜色。
对应关系是从上到下,从左到右:
比如,把帧缓冲区第一个单元的值变为红色值,那么屏幕左上角那个像素点就会自动变为红色。
linux中有一中文件,叫做字符设备文件,每个字符设备(什么是字符设备,4阶段学驱动时会讲)都有一个对应的字符设备文件(Everything is a file ,in linux), Frame Buffer是一个标准的字符设备,对应的文件为 /dev/fb0 (device :设备,所有的设备文件都存在 /dev目录下, fb0: Frame Buffer)
与普通文件不一样,这种设备文件的内容不是存在硬盘中,而是存在内核中,掉电不保存,但是操作方法和普通文件类似。
开发板屏幕一个像素点占4字节,也就是说,每个像素点有 2^32 种颜色可以显示,帧缓冲中的一个单元就是4字节。
2,颜色
RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。
ARGB是一种色彩模式,也就是RGB色彩模式附加上Alpha(透明度)通道,常见于32位位图的存储结构。
不管是 RGB还是ARGB,每个通道都是1字节,取值范围 [0,255],A是高位(24-31),R是次高位(16-23),G是次低位(8-15),B是最低位(0-8)。
不同的 RGB组合成一个不同的颜色值 color
color = A<<24 | R<<16 | G<<8|B
R G B color 0 0 255 0x0000ff 蓝色 0 255 0 0x00ff00 绿色 255 0 0 0xff0000 红色 0 0 0 0x000000 黑色 255 255 255 0xffffff 白色 100 200 255 0x64c8ff ...... 100 << 16 0x640000 200 << 8 0x00c800 255 0x0000ff |-------------- 0x64c8ff 具体颜色可以参考 windows自带的画图板
3,显示屏的基本操作
3.1 打开帧缓冲文件
lcd_fd = open(“/dev/fb0”,O_RDWR); ….
3.2 获取屏幕信息
前面告诉大家,我们开发板的分辨率是480*800,每个想像素点4字节(ARGB),但是如果下次换了一块屏幕,需要用代码获取这些信息。通过 ioctl 函数获取
#include <sys/ioctl.h> int ioctl(int fd, unsigned long request, ...); fd:文件描述符 request:请求命令,每个设备会支持不同的请求,具体支持哪些请求需要和对应的驱动工程去沟通 获取屏幕信息的话,该参数为 FBIOGET_VSCREENINFO 还需要第三个参数,来保存获取到的屏幕信息 struct fb_var_screeninfo 该结构体用来保存屏幕信息,声明在 /usr/include/linux/fb.h 使用这个结构体需要包含 头文件 #include <linux/fb.h> .xres 宽度(一行有多少像素点) .yres 高度(总共有多少行) .bits_per_pixel 一个像素点有多少bit 所以第三个参数应该是 struct fb_var_screeninfo类型变量的地址 示例代码: struct fb_var_screeninfo info; ioctl(lcd_fd,FBIOGET_VSCREENINFO,&info); //如果 ioctl成功了,那么屏幕相关信息就保存在 info中了 lcd_width = info.xres; lcd_height = info.yres; lcd_pixel = info.bits_per_pixel/8;//lcd_pixel表示一个像素点占多少字节
3.3 映射
mmap 功能:把文件或者设备映射到内存,映射成功后就可以通过操作内存达到操作文件或设备的目的,相比直接操作(read/write)文件或设备的优势是:方便、高效
操作映射内存改变帧缓冲内存,从而改变屏幕颜色
lcd_height* #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset); addr:映射内存的首地址,一般设置为NULL,表示由系统决定映射区的起始地址 length:映射区的长度(一般为文件的大小) 但是需要注意的是:实际映射的时候映射内存的大小是 PAGE_SIZE(4096)的整数倍,超出部分 自动清0,操作超出部分没有意义 prot: PROT_EXEC Pages may be executed. PROT_READ Pages may be read. PROT_WRITE Pages may be written. PROT_NONE Pages may not be accessed. 读写权限: PROT_READ | PROT_WRITE flags: MAP_SHARED:共享映射区,怎么理解? 多进程的情况下,一个进行修改了这块内存,其他进行也是可见的。 MAP_PRIVATE:私有的 fd:要映射的文件/设备的文件描述符 offset:偏移量,表示从文件的指定位置开始映射 返回值: 失败返回NULL,同时 errno被设置 成功返回映射区首地址,通过该地址就能够操作整个映射区内存,从而达到改变帧缓冲区内存的效果,最终达到 改变屏幕显示颜色的目的。 lcd_ptr = mmap(NULL,lcd_width*lcd_height*lcd_pixel,PROT_READ | PROT_WRITE, MAP_SHARED,lcd_fd,0); lcd_ptr应该是什么类型的指针? int * ,为什么?因为一个像素点4字节,并且颜色值是整数 假设已经映射成功,怎么操作像素点呢? 为了描述方便,从0开始计数!!! 怎么让第0行第0列那个像素点显示为红色? *lcd_ptr = 0xff0000; 怎么让第0行第1列那个像素点显示为红色? *(lcd_ptr+1) = 0xff0000; 怎么让第0行第2列那个像素点显示为红色? *(lcd_ptr+2) = 0xff0000; 怎么让第0行第x列那个像素点显示为红色? *(lcd_ptr+x) = 0xff0000; 怎么让第1行第x列那个像素点显示为红色? *(lcd_ptr+lcd_width+x) = 0xff0000; 怎么让第y行第x列那个像素点显示为红色? *(lcd_ptr+lcd_width*y+x) = 0xff0000; 在映射成功后,就可以使用 *(lcd_ptr+lcd_width*y+x) = color; 操作任意像素点
3.4 解除映射,关闭文件
int munmap(void *addr, size_t length); addr:映射区内存的首地址,mmap的返回值 lenght:当时映射的长度 失败返回-1,成功返回0 munmap(lcd_ptr,lcd_width*lcd_height*lcd_pixel); close(lcd_fd);
4,显示字符
字符(包括:数字,字母,汉字,符号等一切可以用键盘输入的内容)
需要借助软件: 点阵液晶取模.EXE
可以设置字体。
在文字输入区输入你想显示的字符, Ctrl+Enter组合键结束输入,再点击取模方式中的 c51格式,就会生成该字符对应的取模数据。
生成的数据和该字符的关系:
点阵液晶取模把字符按照上面的规则生成对应的取模数据。
我们要反向操作:通过取模数据还原字的形状
(1)保存取模数据 /*-- 文字: 李 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ unsigned char li[] = { 0x01,0x00,0x01,0x00,0x7F,0xFC,0x03,0x80,0x05,0x40,0x09,0x20,0x31,0x18,0xC1,0x06, 0x0F,0xE0,0x00,0x40,0x00,0x80,0xFF,0xFE,0x01,0x00,0x01,0x00,0x05,0x00,0x02,0x00 }; (2)逐个数据进行分析 int i,j; //for(i=0;i<sizeof(li)/sizeof(unsigned char);i++) for(i=0;i<16*16/8;i++) { //对 li[i] 进行分析,逐个bit进行判断,如果为1,对应位置就显示字的颜色,如果为0,对应位置就显示背景色 //从左往右,从高bit到低bit进行判断 bit7 -> bit0 for(j=7;j>=0;j--)//1字节8bit,循环8次 { if(当前bit为1) { 在对应位置显示字体颜色 } else { 在对应位置显示背景色 } } }
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <unistd.h>
#include <stdio.h>
unsigned char li[] = {
0x01,0x00,0x01,0x00,0x7F,0xFC,0x03,0x80,0x05,0x40,0x09,0x20,0x31,0x18,0xC1,0x06,
0x0F,0xE0,0x00,0x40,0x00,0x80,0xFF,0xFE,0x01,0x00,0x01,0x00,0x05,0x00,0x02,0x00
};
int main()
{
//1打开
int lcd_fd = open("/dev/fb0",O_RDWR);
if(-1 == lcd_fd)
{
perror("");
}
//2获取屏幕信息
struct fb_var_screeninfo info;
ioctl(lcd_fd,FBIOGET_VSCREENINFO,&info);
//如果 ioctl成功了,那么屏幕相关信息就保存在 info中了
int lcd_width = info.xres;
int lcd_height = info.yres;
int lcd_pixel = info.bits_per_pixel/8;//lcd_pixel表示一个像素点占多少字节
//3映射
int * lcd_ptr;
lcd_ptr = mmap(NULL,lcd_width*lcd_height*lcd_pixel,PROT_READ | PROT_WRITE,
MAP_SHARED,lcd_fd,0);
//4在映射成功后,就可以使用 *(lcd_ptr+lcd_width*y+x) = color; 操作任意像素点
//把整块屏幕所有像素点显示成你喜欢的颜色
int color = 0xff;
int x,y;
/*
for(y=0;y<lcd_height;y++)
{
for(x=0;x<lcd_width;x++)
{
*(lcd_ptr+lcd_width*y+x) = color;
//write(lcd_fd,&color,4);
}
}
*/
int i,j;
//for(i=0;i<sizeof(li)/sizeof(unsigned char);i++)
for(i=0;i<16*16/8;i++)
{
//对 li[i] 进行分析,逐个bit进行判断,如果为1,对应位置就显示字的颜色,如果为0,对应位置就显示背景色
//从左往右,从高bit到低bit进行判断 bit7 -> bit0
for(j=7;j>=0;j--)//1字节8bit,循环8次
{
//对应位置: li[i] 的bitj对应点阵的第 y行第x列
y = i/(16/8);
x = (i%(16/8))*8 + (7-j);
if(li[i] & (1<<j))// li[i] 的 bitj
{
*(lcd_ptr+lcd_width*y+x) = 0xff0000;
}
else
{
*(lcd_ptr+lcd_width*y+x) = 0xffffff;
}
}
}
//5,解除映射,关闭屏幕
munmap(lcd_ptr,lcd_width*lcd_height*lcd_pixel);
close(lcd_fd);
}
把显示屏相关的代码合理的封装函数
lcd.c/lcd.h
.h文件中只能写声明语句,不能写定义语句。
int b = 10; int c[5]={1,2,3,4,5}; int sum(int x,int y) { return x+y; } //这些定义语句通通不能出现在 .h 文件中,如果项目中的多个.c文件都包含这个头文件,会导致上面 //的变量、数组、函数重复定义
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
static int lcd_fd; //帧缓冲文件描述符
static int * lcd_ptr;//映射内存的首地址
static int lcd_width;//屏幕宽度
static int lcd_height;//高度
static int lcd_pixel;//一个像素点占几个字节
int init_lcd()
{
//1打开
lcd_fd = open("/dev/fb0",O_RDWR);
if(-1 == lcd_fd)
{
perror("打开帧缓冲文件失败:");
return -1;
}
//2获取屏幕信息
struct fb_var_screeninfo info;
ioctl(lcd_fd,FBIOGET_VSCREENINFO,&info);
//如果 ioctl成功了,那么屏幕相关信息就保存在 info中了
lcd_width = info.xres;
lcd_height = info.yres;
lcd_pixel = info.bits_per_pixel/8;//lcd_pixel表示一个像素点占多少字节
//3映射
lcd_ptr = mmap(NULL,lcd_width*lcd_height*lcd_pixel,PROT_READ | PROT_WRITE,
MAP_SHARED,lcd_fd,0);
if(NULL == lcd_ptr)
{
perror("映射失败:");
close(lcd_fd);
return -1;
}
return 0;
}
void destroy_lcd()
{
munmap(lcd_ptr,lcd_width*lcd_height*lcd_pixel);
close(lcd_fd);
}
void display_point(int x,int y,int color)
{
if(x>=0 && x<lcd_width && y>=0 && y<lcd_height)
*(lcd_ptr+lcd_width*y+x) = color;
}
/*
功能:在屏幕上显示一个字符
参数:
p 要显示的那个字符的取模数组首地址
w,h 要显示的那个字符的宽度和高度
x0,y0 显示的起始位置坐标
c1,c2 分别是背景色和字符颜色
*/
int display_bmp(const char * pathname,int x0,int y0)
{
int fd = open(pathname,O_RDONLY);
if(-1==fd)
{
printf("打开%s失败:%s\n",pathname,strerror(errno));
return -1;
}
char c[2];
read(fd,c,2);
//printf("%c%c\n",c[0],c[1]);
if(c[0] != 'B' || c[1] != 'M')
{
printf("该文件不是BMP格式\n");
close(fd);
return -1;
}
lseek(fd,0x12,SEEK_SET);//把光标定位到 图片宽度位置
int w,h;
read(fd,&w,4);//读取宽度数据保存到变量w中
read(fd,&h,4);//读取高度数据保存到变量h中
//printf("w=%d,h=%d\n",w,h);
int laizi = w%4;//求赖子点数量
lseek(fd,54,SEEK_SET);//定位光标到像素数组位置
int x,y;
unsigned char bgr[3];
int color;
//for(y=0;y<h;y++)
for(y=h-1;y>=0;y--)
{
for(x=0;x<w;x++)
{
read(fd,bgr,3);
color = bgr[0] | bgr[1]<<8 | bgr[2]<<16;
display_point(x+x0,y+y0,color);
}
//每读一行,跳过赖子点
lseek(fd,laizi,SEEK_CUR);
}
close(fd);
return 0;
}
#ifndef __LCD_H__
#define __LCD_H__
//.h文件中写各种声明语句,不能写定义语句
//类型声明,函数声明,外部变量声明 .....
/*
int b = 10;
int c[5]={1,2,3,4,5};
int sum(int x,int y)
{
return x+y;
}
//这些定义语句通通不能出现在 .h 文件中,如果项目中的多个.c文件都包含这个头文件,会导致上面
//的变量、数组、函数重复定义
*/
/*
功能:初始化屏幕(打开,映射),成功之后才能在屏幕上做显示操作
返回值:
失败返回-1,成功返回0
*/
int init_lcd();
/*
功能:释放屏幕资源
*/
void destroy_lcd();
/*
功能:在屏幕上指定位置显示一个点
参数:
x,y 指定像素点的坐标位置
color 指定像素点的颜色
*/
void display_point(int x,int y,int color);
/*
功能:把整块屏幕显示为 color
*/
//void clear(int color);
/*
功能:在屏幕上显示一个字符
参数:
p 要显示的那个字符的取模数组首地址
w,h 要显示的那个字符的宽度和高度
x0,y0 显示的起始位置坐标
c1,c2 分别是背景色和字符颜色
*/
//void display_char(unsigned char *p,int w,int h,int x0,int y0,int c1,int c2);
int display_bmp(const char * pathname,int x0,int y0);
#endif
#include "lcd.h"
//#include "char.h"
int main()
{
int r = init_lcd();
if(-1 == r)
return -1;
display_bmp("./1.bmp",0,0);
destroy_lcd();
return 0;
}
5,显示BMP图片
计算机中基本分为两种:文本文件和二进制文件
文本文件中的内容都是字符,存储的是这些字符的编码,用普通的文本编辑器软件就能打开并显示其内容(字符串)。常见的文本文件有: .txt .c .h .sh ….
二进制文件,内容不是字符串,有其固定的格式和含义。常见的有 a.out .o .bmp .mp3 .mp4 ….,不同的二进制文件中的内容含义是不一样的,如:图片文件中存储了颜色信息,音频文件中存储了声音信息,a.out存储了机器指令 ……
BMP取自位图BitMaP的缩写,也称为DIB(与设备无关的位图),是微软视窗图形子系统(Graphics Device Interface)内部使用的一种位图图形格式,它是微软视窗平台上的一个简单的图形文件格式。
特点如下:
图像通常保存的颜色深度有2(1位)、16(4位)、256(8位)、65536(16位)和1670万(24位)种颜色(其中位是表示每点所用的数据位)。这是一个BMP图片的发展过程,目前来讲一般都是 24位的BMP图片,也就是说每个像素点占24bit/3字节,RGB。
BMP文件通常是不压缩的,所以它们通常比同一幅图像的压缩图像文件格式要大很多。例如,一个800×600的24位几乎占据1.4MB空间。因此它们通常不适合在因特网或者其他低速或者有容量限制的媒介上进行传输。
如 .png .jpg .jpeg 等格式的图片都是通过压缩算法进行压缩的,打开该图片的时候需要解压缩才能获取信息。
我们要在开发板屏幕上显示图片,采用 .bmp图片相对简单,因为不需要解压缩。
24位bmp图片的大小
54是位图文件的文件头(是指文件开始的54字节),文件头中保存了该bmp图片的一些信息,具体如下:
像素数组按照从下到上从左到右的顺序依次存储每个像素点的 BGR数据
但是,每一行的末尾通过填充若干个字节的数据(并不一定为0)使该行的长度为4字节的倍数。像素数组读入内存后,每一行的起始地址必须为4的倍数。
例如:对于24位色的位图,如果它的宽度为1像素,那么除了每一行的数据(蓝、绿、红)需要占3字节外,还会填充1字节;而如果宽为2像素,则需要2字节的填充;宽为3像素时,需要3字节填充;宽为4像素时则不需要填充。
我们一般把每行填充的字节叫做“赖子点”。
赖子点怎么计算?
int laizi = width%4;