Linux Framebuffer应用编程


framebuffer内核驱动学习

一、LCD控制原理

  • Linux系统通过Framebuffer(帧缓冲)驱动程序控制LCD。
  • 显示设备也被称为帧缓冲设备。
  • Frame表示帧,buffer表示缓冲。这就是说,Framebuffer是一块内存,里面存放着一帧图像,每帧图像包含每个像素颜色值。
  • BPP,像素深度,指存储每个像素所用的位数,通常值为16,24,或32。
  • Framebuffer应用编程的本质:修改指定位置像素点颜色值。(重点理解)
  • 分辨率:以”像素点“为单位,屏幕的宽度与高度。例如:分辨率1024 x 600

二、控制LCD

1、控制LCD主要步骤

  1. 驱动程序设置好LCD控制器:
    根据LCD参数设置LCD控制器的时序、信号极性;
    根据LCD分辨率、BPP分配 Framebuffer。
  2. APP使用ioctl获得LCD分辨率、BPP。
  3. APP通过mmap映射Framebuffer,往Framebuffer写入数据。
    1

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跟驱动程序交互:可以传数据给驱动程序,也可以从驱动程序中读出数据。
2

 #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获取屏幕的可变参数,
获取到的参数要放进专用的结构体:
4

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地址,如下图所示:
3
(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,如下图所示
5

  • 对于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. 画点

  1. 行处理(图片按行取模)
  2. 图片显示

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;
}
  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

好好睡觉好好吃饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值