20. 屏幕显示

20.1. Framebuffer介绍

Framebuffer是用一个视频输出设备从包含完整的帧数据的一个内存缓冲区中来驱动一个视频显示设备。 也就是说Framebuffer是一块内存保存着一帧的图像,向这块内存写入数据就相当于向屏幕中写入数据, 如果使用32位的数据来表示一个像素点(使用BBP表示),假设屏幕的显示频分辨率为1920x1080, 那么Framebuffer所需要的内存为1920x1080x32÷4=8,294,400字节约等于7.9M。

简单来说Framebuffer把屏幕上的每个点映射成一段线性内存空间, 程序可以简单的改变这段内存的值来改变屏幕上某一点的颜色。

20.2. 使能lcd相关设备树插件

野火imx6ull提供了很多的设备树插件,用于控制板子上外设的开启,默认状态下lcd相关插件是开启的, 可查看修改 /boot/uEnv.txt 文件内容以确认是否开启了imx-fire-lcd.dtbo设备树插件,如下所示

/boot/uEnv.txt
1
dtoverlay=/usr/lib/linux-image-4.19.35-imx6/overlays/imx-fire-lcd.dtbo

20.2.1. 简单测试

前面提到可以理解Framebuffer把屏幕上的每个点映射成一段线性内存空间, 程序可以简单的改变这段内存的值来改变屏幕上某一点的颜色。 可以使用以下命令不断地往 /dev/fb0 写入随机数据,使得屏幕花屏。

1
cat /dev/urandom > /dev/fb0

20.2.2. fbset工具使用

1
2
3
#安装fbset工具
sudo apt update
sudo apt install fbset

安装完fbset工具之后可以使用 fbset命令查看显示屏相关参数

1
2
3
4
5
6
7
8
9
root@npi:/home/zhan# fbset

mode "800x480-59"
   # D: 27.000 MHz, H: 31.070 kHz, V: 59.069 Hz
   geometry 800 480 800 480 16
   timings 37037 46 22 23 22 1 1
   sync 0x40000000
   rgba 5/11,6/5,5/0,0/0
endmode

fbset工具很强大不仅可以插件显示设备相关参数,同时也能够在用户空间修改lcd参数。

20.3. Framebuffer相关数据结构与ioctl函数

20.3.1. 获取LCD参数

LCD驱动程序给APP提供2类参数:可变的参数 fb_var_screeninfo、固定的参数 fb_fix_screeninfo。 编写应用程序时主要关心可变参数,其结构体定义如下:

20.3.1.1. fb_var_screeninfo结构体

#include <linux/fb.h>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct fb_var_screeninfo {
   __u32 xres;                       /* visible resolution           */
   __u32 yres;
   __u32 xres_virtual;               /* virtual resolution           */
   __u32 yres_virtual;
   __u32 xoffset;                    /* offset from virtual to visible */
   __u32 yoffset;                    /* resolution                   */

   __u32 bits_per_pixel;             /* guess what                   */
   __u32 grayscale;          /* 0 = color, 1 = grayscale,    */
               /* >1 = FOURCC                        */
   struct fb_bitfield red;           /* bitfield in fb mem if true color, */
   struct fb_bitfield green; /* else only length is significant */
   struct fb_bitfield blue;
   struct fb_bitfield transp;        /* transparency                 */

   __u32 nonstd;                     /* != 0 Non standard pixel format */

   __u32 activate;                   /* see FB_ACTIVATE_*            */

   __u32 height;                     /* height of picture in mm    */
   __u32 width;                      /* width of picture in mm     */

   __u32 accel_flags;                /* (OBSOLETE) see fb_info.flags */

   /* Timing: All values in pixclocks, except pixclock (of course) */
   __u32 pixclock;                   /* pixel clock in ps (pico seconds) */
   __u32 left_margin;                /* time from sync to picture    */
   __u32 right_margin;               /* time from picture to sync    */
   __u32 upper_margin;               /* time from sync to picture    */
   __u32 lower_margin;
   __u32 hsync_len;          /* length of horizontal sync    */
   __u32 vsync_len;          /* length of vertical sync      */
   __u32 sync;                       /* see FB_SYNC_*                */
   __u32 vmode;                      /* see FB_VMODE_*               */
   __u32 rotate;                     /* angle we rotate counter clockwise */
   __u32 colorspace;         /* colorspace for FOURCC-based modes */
   __u32 reserved[4];                /* Reserved for future compatibility */
};

仅仅编写应用程序,我们需要关注的成员只有以下几个

  • xres:用于表示lcd的x坐标长度。

  • yres:用于表示lcd的y坐标长度。

  • bits_per_pixel:用于表示屏幕的bbp

20.3.1.2. fb_fix_screeninfo

#include <linux/fb.h>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
struct fb_fix_screeninfo {
   char id[16];                      /* identification string eg "TT Builtin" */
   unsigned long smem_start; /* Start of frame buffer mem */
               /* (physical address) */
   __u32 smem_len;                   /* Length of frame buffer mem */
   __u32 type;                       /* see FB_TYPE_*                */
   __u32 type_aux;                   /* Interleave for interleaved Planes */
   __u32 visual;                     /* see FB_VISUAL_*              */
   __u16 xpanstep;                   /* zero if no hardware panning  */
   __u16 ypanstep;                   /* zero if no hardware panning  */
   __u16 ywrapstep;          /* zero if no hardware ywrap    */
   __u32 line_length;                /* length of a line in bytes    */
   unsigned long mmio_start; /* Start of Memory Mapped I/O   */
               /* (physical address) */
   __u32 mmio_len;                   /* Length of Memory Mapped I/O  */
   __u32 accel;                      /* Indicate to driver which     */
               /*  specific chip/card we have        */
   __u16 capabilities;               /* see FB_CAP_*                 */
   __u16 reserved[2];                /* Reserved for future compatibility */
};

对于固定的参数 fb_fix_screeninfo,在应用编程中很少用到,主要包含比如图形硬件上实际的帧缓存空间的大小、 能否硬件加速等信息。其中 smem_start 表示的是Framebuffer的物理地址。

20.3.2. ioctl函数

需要使用ioctl函数来获取Framebuffer的结构体信息,其函数原型如下

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

request常用的参数如下

FBIOGET_VSCREENINFO

获取可变参数fb_var_screeninfo结构体

FBIOGET_FSCREENINFO

获取固定参数fb_fix_screeninfo结构体

FBIOPUT_VSCREENINFO

设置可变参数fb_var_screeninfo结构体

20.4. LCD显示控制

根据以上信息可编写相关LCD相关程序,如下所示

代码较长复制粘贴有可能会排序不正常,可从这里下载 framebuffer.c

ebf_6ull_quick_start_code/Source/framebuffer/framebuffer.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include <linux/fb.h>
#include <linux/input.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

//RGB565 颜色
#define RED     0xF800       //红色
#define GREED   0x07E0       //绿色
#define BLUE 0x001f  //蓝色
#define WTITE   0xFFFF       //白色
#define BLACK   0x0000       //黑色

static struct fb_var_screeninfo var; /* LCD可变参数 */
static unsigned char *fb_base;                       /* Framebuffer映射基地址 */
static int screen_size;                                      /* 整个Framebuffer大小*/

void lcd_show_pixel_rgb565(int x,int y, unsigned short color);
void lcd_fill_rgb565(unsigned short color);
int get_sta();

int main(int argc, char * argv[])
{
   int i;
   int fd_fb;                                                        /* 文件句柄 */

   fd_fb =  open("/dev/fb0", O_RDWR); //打开LCD文件

   if(fd_fb < 0)
   {
      perror("/dev/fb0");
      exit(-1);
   }

   //获取屏幕可变参数相关信息
   if (ioctl(fd_fb,FBIOGET_VSCREENINFO,&var))
   {
      printf("can't get fb_var_screeninfo \n");
      goto err1;
   }

   printf("X:%d  Y:%d  bbp:%d\n",var.xres,var.yres,var.bits_per_pixel);

   screen_size = var.xres *var.yres *var.bits_per_pixel /8;  //整个Framebuffer大小

   //建立内存映射 方便控制
   fb_base = (unsigned char*)mmap(NULL,screen_size,PROT_READ|PROT_WRITE,MAP_SHARED, fd_fb,0);
   if(fb_base == NULL)
   {
      printf("can't mmap Framebuffer\n");
      goto err1;
   }
   //屏幕闪烁
   lcd_fill_rgb565(WTITE);
   lcd_fill_rgb565(RED);
   sleep(1);
   lcd_fill_rgb565(GREED);
   sleep(1);
   lcd_fill_rgb565(BLUE);
   sleep(1);
   lcd_fill_rgb565(WTITE);
   get_sta();
   //显示线条
   for(i = 0;i<100;i++)
   {
      lcd_show_pixel_rgb565(var.xres/2,var.yres/2+i,RED);
   }

   //解除内存映射关系
   munmap(fb_base,screen_size);
   close(fd_fb);
   return 0;

err1:
   close(fd_fb);
   return -1;
}

int get_sta()
{
   int fd_ts;

   unsigned int x,y;

   struct input_event buf;
   fd_ts=open("/dev/input/event2",O_RDWR);

   if(fd_ts < 0)
   {
      printf("open ts error\r\n");

      return -1;

   }
   while(1)
   {
      read(fd_ts, &buf, sizeof buf);
      if(buf.type == EV_ABS|| buf.type==EV_KEY)
      {
         if(buf.code == ABS_X)
         {
            x = buf.value;
         }
         if(buf.code == ABS_Y)
         {
            y = buf.value;
         }
      }
      if(buf.code == ABS_X)
      {
         x = buf.value;

      }

      if(buf.code == ABS_Y)
      {
         y = buf.value
      }
      lcd_show_pixel_rgb565(x,y,BLUE);
      lcd_show_pixel_rgb565(x-1,y-1,BLUE);
      lcd_show_pixel_rgb565(x-1,y+1,BLUE);
      lcd_show_pixel_rgb565(x+1,y+1,BLUE);
      lcd_show_pixel_rgb565(x+1,y-1,BLUE);
      lcd_show_pixel_rgb565(x,y-1,BLUE);
      lcd_show_pixel_rgb565(x,y+1,BLUE);
      lcd_show_pixel_rgb565(x+1,y,BLUE);
      lcd_show_pixel_rgb565(x-1,y,BLUE);
   }
   return 0;
}


//画点函数
void lcd_show_pixel_rgb565(int x,int y, unsigned short color)
{
   //确定坐标点所在的内存
   void *pen = fb_base + ((var.xres * y + x) * (var.bits_per_pixel/8));

   if( (x > var.xres) ||  (y > var.yres) )
   {
      return ;
   }
   else
   {
      *((unsigned short*)pen) = color;
   }
}

//屏幕填充函数
void lcd_fill_rgb565(unsigned short color)
{
   int i;

   unsigned short  *base = (unsigned short*)fb_base;
   for(i = 0 ;i < (screen_size/2); i++)
   {
      *(base+i) = color;
   }
}

20.5. 编译运行

将代码放到板子上(可以wget,NFS,TFTP等方法),然后在板子上运行如下代码

1
2
3
 sudo gcc framebuffer.c -o LCD_show
 sudo chmod 777 LCD_show
 ./LCD_show

程序现象也是相对简单,红色、绿色、蓝色、白色各显示一遍之后,如果是触摸屏可在上进行绘制。

未找到图片233

如果要修改LCD的引脚可参考 修改lcd相关设备树 小节

20.6. fire-config修改液晶参数

在终端执行sudo fire-config命令,选择“Device”项,如下图:

fire-config01

选择“LCD”项

fire-config02

使用空格选中“C2 General LCD”项。

fire-config03

选中回车之后即可根据显示屏说明文档配置修改LCD相关配置信息。

fire-config04