19.Frambuffer应用编程

        在Linux系统中显示设备被归类为framebuffer设备即帧缓冲设备。对于应用层来说,Linux系统把LCD等显示设备抽象为一块显存缓冲区,允许上层应用程序直接对该显存缓冲区进行读写操作,不必关心硬件的物理地址,大大简化了读写速度和方便性。

一、使用ioctl函数读取屏幕的参数信息

        想要操作一块LCD屏幕,首先得明确了解屏幕的参数信息,比如分辨率等等。这样才能计算出显示缓冲区应该设置的大小。读取信息采用ioctl函数。

ioctl函数

#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
第一个参数fd为文件描述符
第二个参数为request,对于LCD屏幕来说常用的request有
FBIOGET_VSCREENINFO:获取LCD的可变参数信息
FBIOPUT_VSCREENINFO: 设置LCD的可变参数信息 
FBIOGET_FSCREENINFO: 获取LCD的不变参数信息
第三个参数为保存信息的结构体。

FBIOGET_VSCREENINFO FBIOPUT_VSCREENINFO

FBIOGET_FSCREENINFO 以及 2 个数据结构 struct fb_var_screeninfo struct fb_fix_screeninfo 都定义在<linux/fb.h>头文件中

获取LCD屏幕信息: 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/fb.h>

int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;    //保存屏幕的固定参数
    struct fb_var_screeninfo fb_var;    //保存屏幕的可变参数
    int fd; //保存文件描述符

    //检查命令行参数是否正确
    if(argc != 2) {
        fprintf(stderr, "Error used!\n");
        exit(-1);
    }

    //打开LCD文件
    fd = open(argv[1], O_RDWR); //读写方式打开
    if(fd < 0) {
        perror("open failed!\n");
        exit(-1);
    }

    /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    printf("分辨率: %d*%d\n"
    "像素深度 bpp: %d\n"
    "一行的字节数: %d\n"
    "像素格式: R<%d %d> G<%d %d> B<%d %d>\n",
    fb_var.xres, fb_var.yres, fb_var.bits_per_pixel,
    fb_fix.line_length,
    fb_var.red.offset, fb_var.red.length,
    fb_var.green.offset, fb_var.green.length,
    fb_var.blue.offset, fb_var.blue.length);
    /* 关闭设备文件退出程序 */
    close(fd);
    exit(0);
}

运行结果如下:

二、使用mmap函数将显示缓冲区映射到用户空间

        对于Framebuffer编程普通的I/O读写方式,对于数据量比较大的时候效率低并且很麻烦。因此通常采用存储映射I/O的方式将显示器的显示缓冲区(显存)映射到进程的地址空间中,这样应用程序便可直接对显示缓冲区进行读写操作。由系统调用 mmap()来实现。 其函数原型如下所示:

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);


使用该函数需要包含头文件<sys/mman.h>。
函数参数和返回值含义如下:
addr: 参数 addr 用于指定映射到内存区域的起始地址。通常将其设置为 NULL,这表示由系统选择该
映射区的起始地址,这是最常见的设置方式;如果参数 addr 不为 NULL,则表示由自己指定映射区的起始
地址,此函数的返回值是该映射区的起始地址。
length: 参数 length 指定映射长度, 表示将文件中的多大部分映射到内存区域中,以字节为单位,譬如
length=1024 * 4,表示将文件的 4K 字节大小映射到内存区域中。
offset: 文件映射的偏移量,通常将其设置为 0,表示从文件头部开始映射;所以参数 offset 和参数 length就确定了文件的起始位置和长度, 将文件的这部分映射到内存区域中
fd: 文件描述符,指定要映射到内存区域中的文件。
prot: 参数 prot 指定了映射区的保护要求,可取值如下:
    PROT_EXEC: 映射区可执行;
    PROT_READ: 映射区可读;
    PROT_WRITE: 映射区可写;
    PROT_NONE: 映射区不可访问。
flags: 参数 flags 可影响映射区的多种属性, 参数 flags 必须要指定以下两种标志之一:⚫ MAP_SHARED: 此标志指定当对映射区写入数据时,数据会写入到文件中,也就是会将写入到映
射区中的数据更新到文件中,并且允许其它进程共享。
    MAP_PRIVATE: 此标志指定当对映射区写入数据时,会创建映射文件的一个私人副本(copy-onwrite),对映射区的任何操作都不会更新到文件中,仅仅只是对文件副本进行读写。
除此之外,还可将以下标志中的 0 个或多个组合到参数 flags 中,通过按位或运算符进行组合:
    MAP_FIXED: 在未指定该标志的情况下,如果参数 addr 不等于 NULL,表示由调用者自己指定
映射区的起始地址,但这只是一种建议、而并非强制,所以内核并不会保证使用参数 addr 指定的
值作为映射区的起始地址;如果指定了 MAP_FIXED 标志,则表示要求必须使用参数 addr 指定的
值作为起始地址,如果使用指定值无法成功建立映射时,则放弃!通常,不建议使用此标志,因为
这不利于移植。
    MAP_ANONYMOUS: 建立匿名映射, 此时会忽略参数 fd 和 offset,不涉及文件,而且映射区域
无法和其它进程共享。
    MAP_ANON: 与 MAP_ANONYMOUS 标志同义,不建议使用。
    MAP_DENYWRITE: 该标志被忽略。
    MAP_EXECUTABLE: 该标志被忽略。
    MAP_FILE: 兼容性标志,已被忽略。
    MAP_LOCKED: 对映射区域进行上锁。
除了以上标志之外,还有其它一些标志,这里便不再介绍,可通过 man 手册进行查看。 在众多标志当
中,通常情况下,参数 flags 中只指定了 MAP_SHARED。
返回值: 成功情况下,函数的返回值便是映射区的起始地址;发生错误时,返回(void *)-1, 通常使用
MAP_FAILED 来表示, 并且会设置 errno 来指示错误原因。

三、LCD应用编程基本操作------水平方向

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/fb.h>

static int width; //LCD X 分辨率
static int height; //LCD Y 分辨率
static unsigned short *screen_base = NULL; //映射后的显存基地址



static void lcd_draw_point(unsigned int x, unsigned int y, unsigned int color); //画点函数
static void lcd_draw_line(unsigned int x, unsigned int y, int dir,unsigned int length, unsigned int color);//画线函数
static void lcd_draw_rectangle(unsigned int start_x, unsigned int end_x,unsigned int start_y, unsigned int end_y,unsigned int color);    //画矩形
static void lcd_fill(unsigned int start_x, unsigned int end_x,unsigned int start_y, unsigned int end_y,unsigned int color); //填充矩形
unsigned int argb8888_to_rgb565(unsigned int color);    //RGB88 to RGB565

int main(int argc, char *argv[])
{
    struct fb_fix_screeninfo fb_fix;    //保存屏幕的不变参数
    struct fb_var_screeninfo fb_var;    //保存屏幕的可变参数
    unsigned int screen_size;
    int fd;

    if(argc != 2) {
        fprintf(stderr, "Erroe used!\n");
        exit(-1);
    }

    fd = open(argv[1], O_RDWR);
    if(fd < 0) {
        perror("open failed!\n");
        exit(-1);
    }

        /* 获取参数信息 */
    ioctl(fd, FBIOGET_VSCREENINFO, &fb_var);
    ioctl(fd, FBIOGET_FSCREENINFO, &fb_fix);

    /*计算屏幕尺寸*/
    screen_size = fb_fix.line_length * fb_var.yres;
    width = fb_var.xres;
    height = fb_var.yres;
    printf("width = %d,height = %d\n",width,height);

    /*将显示缓冲区映射到用户空间*/
    screen_base = mmap(NULL, screen_size, PROT_WRITE, MAP_SHARED, fd, 0);
    if ((void *)screen_base == MAP_FAILED) {
        perror("mmap error");
        close(fd);
        exit(-1);
    }
    
    /* 画线: 十字交叉线 */
    lcd_draw_line(0, height * 0.5, 1, width, 0xFFFFFF);//白色线
    lcd_draw_line(width * 0.5, 0, 0, height, 0xFFFFFF);//白色线

    /* 退出 */
    munmap(screen_base, screen_size); //取消映射
    close(fd); //关闭文件
    exit(0); //退出进程
}

unsigned int argb8888_to_rgb565(unsigned int color) 
{
    unsigned int temp = (color);
    temp = (temp & 0xF80000UL) >> 8 | (temp & 0xFC00UL) >> 5 | (temp & 0xF8UL) >> 3;
    return temp;
}

static void lcd_draw_point(unsigned int x, unsigned int y, unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color);//得到 RGB565 颜色值
    /* 对传入参数的校验 */
    if (x >= width)
    x = width - 1;
    if (y >= height)
    y = height - 1;
    /* 填充颜色 */
    screen_base[y * width + x] = rgb565_color;
}


static void lcd_draw_line(unsigned int x, unsigned int y, int dir,unsigned int length, unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color);//得到 RGB565 颜色值
    unsigned int end;
    unsigned long temp;
    /* 对传入参数的校验 */
    if (x >= width)
    x = width - 1;
    if (y >= height)
    y = height - 1;
    /* 填充颜色 */
    temp = y * width + x;//定位到起点
    if (dir) { //水平线
        end = x + length - 1;
    if (end >= width)
        end = width - 1;
    for ( ; x <= end; x++, temp++)
        screen_base[temp] = rgb565_color;
    }
    else { //垂直线
        end = y + length - 1;
    if (end >= height)
        end = height - 1;
    for ( ; y <= end; y++, temp += width)
        screen_base[temp] = rgb565_color;
    }
}

static void lcd_draw_rectangle(unsigned int start_x, unsigned int end_x,
unsigned int start_y, unsigned int end_y,
unsigned int color)
{
    int x_len = end_x - start_x + 1;
    int y_len = end_y - start_y - 1;
    lcd_draw_line(start_x, start_y, 1, x_len, color);//上边
    lcd_draw_line(start_x, end_y, 1, x_len, color); //下边
    lcd_draw_line(start_x, start_y + 1, 0, y_len, color);//左边
    lcd_draw_line(end_x, start_y + 1, 0, y_len, color);//右边
}

static void lcd_fill(unsigned int start_x, unsigned int end_x,
unsigned int start_y, unsigned int end_y,
unsigned int color)
{
    unsigned short rgb565_color = argb8888_to_rgb565(color);//得到 RGB565 颜色值
    unsigned long temp;
    unsigned int x;
    /* 对传入参数的校验 */
    if (end_x >= width)
        end_x = width - 1;
    if (end_y >= height)
        end_y = height - 1;
    /* 填充颜色 */
        temp = start_y * width; //定位到起点行首
    for ( ; start_y <= end_y; start_y++, temp+=width) {
        for (x = start_x; x <= end_x; x++)
        screen_base[temp + x] = rgb565_color;
}
}

代码流程:

        首先调用 open()打开 LCD 设备文件得到文件描述符 fd;
        接着使用 ioctl 函数获取 LCD 的可变参数信息和固定参数信息,通过得到的信息计算 LCD 显存大小、得到 LCD 屏幕的分辨率,从图 19.3.1 可知, ALPHA/Mini I.MX6U 开发板出厂系统将 LCD 实现为一个 RGB565 显示设备,所以程序中自定义的 4 个函数在操作 LCD 像素点时、都是以 RGB565的格式写入颜色值。
        接着使用 mmap 建立映射;
        映射成功之后就可以在应用层直接操作 LCD 显存了,调用自定义的函数在 LCD 上画线
        操作完成之后,调用 munmap 取消映射,调用 close 关闭 LCD 设备文件,退出程序。

四、LCD应用编程基本操作------垂直方向

        有时候需要进行垂直显示也就是将本屏幕变为600 * 1024分辨率,其实很简单只需要在计算坐标的时候改变一下公式即可实现。

        水平方向像素点位置计算公式:

screen_base[y * width + x]

        垂直方向像素点位置计算公式:

screen_base[(height - 1- x) * width + y)];
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值