文章目录
framebuffer内核驱动学习
一、LCD控制原理
- Linux系统通过Framebuffer(帧缓冲)驱动程序控制LCD。
- 显示设备也被称为帧缓冲设备。
- Frame表示帧,buffer表示缓冲。这就是说,Framebuffer是一块内存,里面存放着一帧图像,每帧图像包含每个像素颜色值。
- BPP,像素深度,指存储每个像素所用的位数,通常值为16,24,或32。
- Framebuffer应用编程的本质:修改指定位置像素点颜色值。(重点理解)
- 分辨率:以”像素点“为单位,屏幕的宽度与高度。例如:分辨率1024 x 600
二、控制LCD
1、控制LCD主要步骤
- 驱动程序设置好LCD控制器:
根据LCD参数设置LCD控制器的时序、信号极性;
根据LCD分辨率、BPP分配 Framebuffer。 - APP使用ioctl获得LCD分辨率、BPP。
- APP通过mmap映射Framebuffer,往Framebuffer写入数据。
2、涉及函数
2.1、open函数/colse函数
需要头文件:#include<sys/stat.h>
#include<fcntl.h>
函数原型:int open(const char *pathname,int flags,int perms);
函数参数:pathname:打开文件名(可以包含具体路径名)
flags:打开文件的方式,具体见下
perms:新建文件的权限,可以使用宏定义或者八进制文件权限码,具体见下
函数返回值:成功:文件描述符,失败:-1
参数2flags具体可用参数(若使用多个flags参数可以使用‘|’符号来组合):
O_RDONLY:以只读方式打开文件
O_WRONLY:以只写方式打开文件
O_RDWR:以可读可写方式打开文件
注意:O_RDONLY与O_WRONLY与O_RDWR三个参数互斥,不可同时使用
/*其他可选*/
O_CREAT:如果文件不存在,就创建这个文件,并使用参数3为其设置权限
O_EXCL:如果使用O_CREAT创建文件时文件已存在则返回错误信息。使用这个参数可以测试文件是否已存在
O_NOCTTY:若打开的是一个终端文件,则该终端不会成为当前进程的控制终端
O_TRUNC:若文件存在,则删除文件中全部原有数据并设置文件大小为0
O_APPEND:以添加形式打开文件,在对文件进行写数据操作时数据添加到文件末尾
若在参数2的位置有多个参数进行组合,注意使用按位或(|)运算符。
/可查看/usr/include/i386-linux-gnu/bits/fcntl.h文件看到具体的宏定义 /
2.2、ioctl函数
ioctl的作用非常强大、灵活。不同的驱动程序内部会实现不同的ioctl,APP可以使用各种ioctl跟驱动程序交互:可以传数据给驱动程序,也可以从驱动程序中读出数据。
#include <sys/ioctl.h>
函数原型:
int ioctl(int fd, unsigned long request, ...);
ioctl( 文件描述符, 宏(获取数据的方法),数据存储区域的首地址 )
1.功能:从设备文件中按照某种方法读取数据
2.参数:
@fd 表示文件描述符;
@request表示与驱动程序交互的命令,
用不同的命令控制驱动程序输出我们需要的数据;
@… 表示可变参数arg,根据request命令,设备驱动程序返回输出的数据。
3.LCD参数request宏:
linux ->/usr/include/linux/fb.h:
#define FBIOGET_VSCREENINFO 0x4600 -->获取屏幕的可变参数
#define FBIOPUT_VSCREENINFO 0x4601 -->写入/修改屏幕的可变参数
#define FBIOGET_FSCREENINFO 0x4602 -->获取屏幕的固定参数
4.返回值: 成功0,失败-1,并且将详细错误信息赋值给errno全局变量。
如果要使用宏FBIOGET_VSCREENINFO获取屏幕的可变参数,
获取到的参数要放进专用的结构体:
2.3、mmap函数/munmap函数
作为APP开发,只需要知道它的用法就可以了。
头文件: #include <sys/mman.h>
函数原型:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
功能:进行内存映射
函数参数:
@addr : 映射的内存的地址(一般自己不指定地址,写为NULL,内核会自动分配地址)
@length : 映射的内存大小(长度) ,单位:字节
@port : 映射的内存的权限
PROT_EXEC 映射区域可被执行
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
PROT_NONE 映射区域不能访问
@flags : 表示影响映射区域的不同特性
MAP_SHARED 表示对映射区域写入的数据会复制回文件内,原来的文件会改变。
MAP_PRIVATE 表示对映射区域的操作会产生一个映射文件的复制,
对此区域的任何修改都不会写回原来的文件内容中。
@fd : 设备的文件描述符
@offset : 偏移量,从哪里开始映射
返回值:成功返回映射好的内存区域的首地址,失败返回宏-MAP_FAILED (that is, (void *) -1)
-------------------------------------------------------------------------------------------------------
int munmap(void *addr, size_t length);
取消内存映射
3、设置显示屏每个上像素点的颜色的原理(画点原理)
3.1、找到这个像素对应的内存地址
假设fb_base是APP执行mmap后得到的Framebuffer地址,如下图所示:
(x,y)坐标处像素对应的Framebuffer地址:
(x,y)像素起始地址=fb_base+(xres*bpp/8)*y + x*bpp/8
bpp: bits per pixel 每个像素用多少位来表示它的颜色(位深)(像素深度)
xres:像素宽度
fb_base是程序执行mmap后得到的Framebuffer地址
3.2、根据它的BPP值设置颜色
RGB颜色表链接
RGB三原色(红、绿、蓝)来表示的,在不同的BPP格式中,用不同的位来分别表示R、G、B,如下图所示
- 对于32BPP,一般只设置其中的低24位,高8位表示透明度,一般的LCD都不支持。
- 对于24BPP,硬件上为了方便处理,在Framebuffer中也是用32位来表示,效果跟32BPP是一样的。
- 对于16BPP,常用的是RGB565;很少的场合会用到RGB555,这可以通过ioctl读取驱动程序中的RGB位偏移来确定使用哪一种格式。
3.3、格式转换
我们使用的格式永远是0x00RRGGBB,即RGB888。
当LCD是16bpp时,要把color变量中的R、G、B抽出来再合并成RGB565格式。
先从color变量中把R、G、B抽出来。
把red、green、blue这三种8位颜色值,根据RGB565的格式,只保留red中的高5位、green中的高6位、blue中的高5位,组合成一个新的16位颜色值。
再把新的16位颜色值写入Framebuffer。
三、代码
编写思路
1.打开设备文件
2.获取LCD设备信息参数
3. 映射Framebuffer
4. 画点
- 行处理(图片按行取模)
- 图片显示
my_fb.h文件
#ifndef __MY_FB_H__
#define __MY_FB_H__
#include <stdio.h>
#include <linux/input.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/ioctl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <string.h>
#define RED 0xFF0000
#define ORANGE 0xFF8000
#define YELLOW 0xFFFF00
#define GREEN 0x00FF00
#define CYAN 0x00FFFF
#define BLUE 0x0000FF
#define PURPLE 0x800080
#define BLACK 0x000000
#define WHITE 0xFFFFFF
#define SILVER 0xC0C0C0
#define IvyGreen 0x36BF36
#define Malachite 0x22C32E //孔雀石绿
#define FB_PATHNAME "/dev/fb0"
/* 全局变量 */
int fd_fb0; /* 设备fb0的文件描述符 */
int fb_size; /* fb设备内存大小 */
void *p_fb; /* 指向mmap映射的内存首地址 */
struct fb_var_screeninfo vinfo; /* 存放fb可变参数 */
/* 函数声明 */
int fbInit(const char *devicepathname); /* 打开设备,获取设备信息 */
void fbSetPixel(unsigned int x, unsigned int y, int color); /* 画点 */
//行处理
void fbSetLineData(unsigned int x, unsigned int y, unsigned int width, unsigned char *pLine);
void fbDisplayImage_16(unsigned int x, unsigned int y, unsigned int w, unsigned int h, const unsigned char *pImage); //16位显示
void fbDisplayImage_24(unsigned int x, unsigned int y, unsigned int w, unsigned int h, const unsigned char *pImage); //24位显示
void screenClean(int color); /* 清屏 */
void fbDestroy(); /* 解除映射 */
#endif
#endif
my_fb.c文件
#include "my_fb.h"
/*
功能:打开设备,获取设备信息
参数:设备文件地址
调用:外部调用
*/
int fbInit(const char *devicepathname){
/* 1.找到并打开设备fb0 */
if( -1 == (fd_fb0 = open(devicepathname, O_RDWR))){
perror("open");
return -1;
}
/* 2.使用ioctl函数获取设备的可变参数 */
if(-1 == ioctl(fd_fb0, FBIOGET_VSCREENINFO, &vinfo)){
perror("ioctl");
return -1;
}
/* 打印一下fb设备可变参数 */
printf("screen width : %u px \n", vinfo.xres); /* 宽像素点 */
printf("screen height : %u px \n", vinfo.yres); /* 高像素点 */
printf("bits per pixel: %u bits \n", vinfo.bits_per_pixel); /* 位深度 */
/* 计算设备所占内存大小 */
fb_size = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8; /* 单位:字节 */
/* 3.内存映射 */
p_fb = mmap(NULL, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb0, 0);
if( p_fb == (void *)-1 ){
perror("mmap");
return -1;
}
return 0;
}
/*
清屏:将全部屏幕设置为函数
调用:外部调用
*/
void screenClean(int color){
int w, h;
for(w = 0;w < vinfo.xres; w++){
for(h = 0; h < vinfo.yres; h++){
fbSetPixel(w, h, color);
}
}
}
/*
功能:画点
参数:
x:图片显示的起始x坐标
y:起始y坐标
color::颜色:RGB888格式
调用:内部调用
*/
void fbSetPixel(unsigned int x, unsigned int y, int color){
/* 传参判断 */
if( x < 0 || x >= vinfo.xres ){
return;
}
if( y < 0 || y >= vinfo.yres ){
return;
}
/* 先确认像素点的位置 */
//一个像素点占多少字节: vinfo.bits_per_pixel /8
//一行像素点占多少字节: vinfo.bits_per_pixel /8 * vinfo.xres
/* 8位 */
unsigned char *pen8 = p_fb + (y * vinfo.bits_per_pixel / 8 * vinfo.xres) + (x * vinfo.bits_per_pixel / 8 );
/* 16位 */
unsigned short *pen16 = (unsigned short *)pen8;
/* 24位 */
unsigned int *pen24 = (unsigned int *)pen8;
int red, green, blue;
unsigned short rgb = 0;
/* 判断屏幕位深度 */
switch( vinfo.bits_per_pixel ){
case 8:{ /* RGB233 */
red = ( (color >> 16) & 0xff );
green = ( (color >> 8) & 0xff );
blue = ( color & 0xff );
rgb = ((red >> 6) << 6) | ((green >> 5) << 3) | (blue >> 5);
*pen8 = rgb;
break;
}
case 16:{ /* RGB565 */
red = ( (color >> 16) & 0xff );
green = ( (color >> 8) & 0xff );
blue = ( color & 0xff );
rgb = ((red >> 3) << 11) | ( (green >> 2) <<5 ) | (blue >> 3);
*pen16 = rgb;
break;
}
case 24:{ /* RGB888 */
*pen24 = color;
break;
}
default:
printf("Can not support bits_per_piexl: %u .\n", vinfo.bits_per_pixel);
}
}
/*
功能:按行处理的函数-处理一行的RGB888颜色数据
参数:
x --> 行起始坐标像素点横坐标
y --> 行起始坐标像素点纵坐标
width --> 一行像素点个数,宽度
pLine --> 一行数据的起始地址
调用:内部调用
*/
void fbSetLineData(unsigned int x, unsigned int y, unsigned int width, unsigned char *pLine){
int i = 0;
int color = 0;
for(i = 0 ;i < width *3 ;i += 3){
color = (pLine[i] << 16) | (pLine[i+1] << 8) | (pLine[i+2]);
fbSetPixel(x, y, color);
x++;
}
}
/*
功能:fb显示图像-取模16位真彩色
参数:
x --> 图像位置的横坐标
y --> 图像位置的纵坐标
w --> 图像宽像素点
h --> 图像高像素点
pImage--> 图像数据源的首地址
调用:外部调用
*/
void fbDisplayImage_16(unsigned int x, unsigned int y, unsigned int w, unsigned int h, const unsigned char *pImage){
/* 要确定显示在LCD屏幕的位置(目标像素点的首地址) */
void *pen = p_fb + (y * vinfo.xres * vinfo.bits_per_pixel / 8) + (x * vinfo.bits_per_pixel / 8 );
void *p = (void *)pImage;
int i = 0;
for(i = 0; i < h; i++){
memcpy(pen, p, w * 2); //像素点*2
pen += vinfo.xres * vinfo.bits_per_pixel / 8;
p += w * 2;
}
}
/*
功能:fb显示图像-取模24位真彩色
参数:
x --> 图像位置的横坐标
y --> 图像位置的纵坐标
w --> 图像宽像素点
h --> 图像高像素点
pImage--> 图像数据源的首地址
调用:外部调用
*/
void fbDisplayImage_24(unsigned int x, unsigned int y, unsigned int w, unsigned int h, const unsigned char *pImage){
unsigned char *pimage = (unsigned char *)pImage; /* 找到图片数据源位置 */
int i = 0 ;
for(i = 0; i < h; i++){
fbSetLineData(x, y, w, pimage); /* 按行处理的函数-处理一行的RGB888颜色数据 */
pimage += w * 3;
y++;
}
}
/*
功能:解除映射
调用:外部调用
*/
void fbDestroy(){
/* 4.解除映射 */
munmap(p_fb, fb_size);
/* 5.关闭文件描述符 */
close(fd_fb0);
}
main函数
#include "my_fb.h"
/* 引入外部变量 */
const unsigned char gImage_aaa[1228800];
int main(int argc, const char *argv[]){
/* fb0设备初始化 */
fbInit(FB_PATHNAME);
/* 清屏 */
screenClean(BLACK);
/* 在LCD屏显示图片 */
fbDisplayImage_16(0, 0, 1024, 600, gImage_aaa);
//fbDisplayImage_24(750, 0, 500, 500, gImage_Image_24);
/* 解除映射 */
fbDestroy();
return 0;
}