Linux LCD Frambuffer基础介绍和使用方法

新闻资讯   2023-06-30 12:02   86   0  

1、什么是Framebuffer?

Framebuffer 字面意思就是帧缓存的意思,即显存,里面保存着一帧图像。事实上,对于嵌入式系统而言,没有真正意义上的显存,Framebuffer 是通过内存模拟出来的。

LCD FrameBuffer 里的若干字节表示(具体根据驱动适配),LCD 屏幕上的一个像素点。

  • RGB888:32bpp,占4字节,分别是A8、R8、G8、B8,一般只用其中低24位,高8位表示透明度。
  • RGB565:16bpp,占2字节,分别是R5、G6、B5,比较常用的一种颜色。
  • RGB555:很少用。

假设LCD屏幕分辨率是800x600,每个像素占4字节,那么framebuffer 大小就是:

800 x 600 x 4 = 960000 字节

framebuffer 显示原理,如下图所示:

假设需要设置 LCD 中坐标(x,y)处像素的颜色,首要要找到这个像素对应的内存,然后根据它的 BPP 值设置颜色。

假设 fb_base 是 APP 执行 mmap 后得到的 Framebuffer 地址,如下图所示:

(x,y)像素起始地址=fb_base+(xres*bpp/8)y + xbpp/8

2、为什么要有Frambuffer?

思考一个问题,为什么要用Framebuffer?

上图为LCD 驱动框架图:

从软件层面分析:framebuffer 起着承上启下的作用,向上,为应用层提供通用系统调用(open(),ioctl(),mmap());向下,联接LCD控制器,之前对硬件进行操作。

从硬件层面分析:用户只需要将数据写到framebuffer,硬件会自动刷新到屏幕上。

3、常用接口和数据结构

3.1 常用接口

  1. 打开设备:open()系统调用。

通过 man 2 查看如下:

函数说明:


  1. ioctl 系统调用:

函数原型:

int ioctl(int fd, unsigned long request, ...);

函数说明:

  • fd :表示文件描述符;

  • request:表示与驱动程序交互的命令,用不同的命令控制驱动程序输出我们需要的数据。

  • … :表示可变参数arg,根据request命令,设备驱动程序返回输出的数据。

  • 返回值:打开成功返回文件描述符,失败将返回-1。

3)mmap 系统调用:

函数说明:

3.2 相关数据结构

fb_var_screeninfo:包含xres, yres, bits_per_pixel等信息,在后续会经常用到。

4、如何在LCD 上描点?

4.1 LCD 显示原理

当我们需要显示一个字母‘A’时,是通过判断点阵的每一个位数值状态,来填充颜色,达到显示字符效果。其中‘1’表示一种颜色,‘0’表示填充另一种颜色。

如下图[1]8*16的点阵,只要有这个点阵,我们就可以在LCD上面描点,达到显示字符的效果。

4.2 Framebuffer 操作说明

framebuffer 操作如下流程:

  • 打开设备(open)
  • 获取屏幕参数信息(ioctl)
  • 分配显存(mmap)
  • 描点/写数据
  • 释放资源(unmmap)
  • 关闭设备(close)
int fd_fb;
struct fb_var_screeninfo var; /* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;

int main(int argc, char *argv[])
{
    /*Step1: 打开设备 */
    fd_fb = open("/dev/fb0", O_RDWR);
 if (fd_fb < 0)
 {
  printf("can't open /dev/fb0\n");
  return -1;
 }
    
    /*Step2:获取设备参数信息 
    * xres:x 方向总像素
    * yres:y 方向总像素
    * bits_per_pixel:每个像素占多少位
    */

 if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
 {
  printf("can't get var\n");
  return -1;
 }

    /* Step3: 计算线宽,分配显存 */
    /* line_width 每行占的字节 */
 line_width  = var.xres * var.bits_per_pixel / 8;
 pixel_width = var.bits_per_pixel / 8;
 screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
 fbmem = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
 if (fbmem == (unsigned char *)-1)
 {
  printf("can't mmap\n");
  return -1;
 }

 /* Step4: 清屏: 全部设为黑色 */
 memset(fbmem, 0, screen_size);

   /* Step5: 描点 */
 lcd_put_ascii(var.xres/2, var.yres/2'A'); /*在屏幕中间显示8*16的字母A*/
 
    /* Step6:释放资源*/
 munmap(fbmem , screen_size);
    /* Step7:关闭设备 */
 close(fd_fb);
 
 return 0;     
}

4.3 描点实现

描点的关键是计算点(x,y)位置对应的地址,然后直接指向fbmem即可向frambuffer 写入数据。

/**********************************************************************
 * 函数名称:lcd_put_pixel
 * 功能描述: 在LCD指定位置上输出指定颜色(描点)
 * 输入参数:x坐标,y坐标,颜色
 * 输出参数: 无
 * 返 回 值: 会
 ***********************************************************************/
 
void lcd_put_pixel(int x, int y, unsigned int color)
{
  /* 
  * 最主要的就是fbmem 
  * 计算(x,y)位置的偏移,然后指向fbmem,这块直接映射到framebuffer 内存里
  */

  unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
  unsigned short *pen_16; 
  unsigned int *pen_32; 
  unsigned int red, green, blue; 

  pen_16 = (unsigned short *)pen_8;
  pen_32 = (unsigned int *)pen_8;

  switch (var.bits_per_pixel)
  {
       /* 8bpp*/
    case 8:
    {
      *pen_8 = color;
      break;
    }
        /*16 bpp */
    case 16:
    {
      /* 565 */
      red   = (color >> 16) & 0xff;
      green = (color >> 8) & 0xff;
      blue  = (color >> 0) & 0xff;
      color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
      *pen_16 = color;
      break;
    }
    case 32:
    {
      *pen_32 = color;
      break;
    }
    default:
    {
      printf("can't surport %dbpp\n", var.bits_per_pixel);
      break;
    }
  }
}

4.4 向 LCD 写入 英文

写入英文的前提:

  • 描点函数已实现;
  • 具备该英文字符的点阵数据;
/**********************************************************************
 * 函数名称:lcd_put_ascii
 * 功能描述: 在LCD指定位置上显示一个8*16的字符
 * 输入参数:x坐标,y坐标,ascii码
 * 输出参数: 无
 * 返 回 值: 无
 ***********************************************************************/
 
void lcd_put_ascii(int x, int y, unsigned char c)
{
    /* fontdata_8x16 8x16 英文点阵数据 */
    unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
    int i, b;
    unsigned char byte;

   /* 8 x 16 的点阵, 16行 8列*/
   for (i = 0; i < 16; i++)
   {
     byte = dots[i];
     for (b = 7; b >= 0; b--)
     {
       if (byte & (1<<b))
       {
         /* show */
         lcd_put_pixel(x+7-b, y+i, 0xffffff); /* 白 */
       }
       else
       {
         /* hide */
         lcd_put_pixel(x+7-b, y+i, 0); /* 黑 */
        }
        }
 }
}

END

来源:漫谈嵌入式

版权归原作者所有,如有侵权,请联系删除。

推荐阅读
你管这破玩意叫CPU?
RTOS实现双核MCU消息通信
入职Linux驱动工程师后,我才知道的真相…

→点关注,不迷路←

文章引用微信公众号"嵌入式微处理器",如有侵权,请联系管理员删除!

博客评论
还没有人评论,赶紧抢个沙发~
发表评论
说明:请文明发言,共建和谐网络,您的个人信息不会被公开显示。