14. 屏幕显示

14.1. Framebuffer介绍

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

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

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

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

/boot/uEnv.txt
1
dtoverlay=/usr/lib/linux-image-4.19.94-stm-r1/overlays/stm-fire-lcd.dtbo

14.2.1. 简单测试

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

1
cat /dev/urandom > /dev/fb0

14.2.2. fbset工具使用

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

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

1
2
3
4
5
6
7
8
root@npi:~# fbset

mode "800x480"
    geometry 800 480 800 480 16
    timings 0 0 0 0 0 0 0
    accel true
    rgba 5/11,6/5,5/0,0/0
endmode

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

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

14.3.1. 获取LCD参数

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

14.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

14.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的物理地址。

我们将在应用程序中使用到 line_length 成员,由于表示显示一行需要用到多少字节。

14.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结构体

14.4. LCD显示控制

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

代码仓库/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
#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 <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 struct fb_fix_screeninfo var1;         /* 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 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;
   }
   //获取固定参数
   if (ioctl(fd_fb,FBIOGET_FSCREENINFO,&var1))
   {
      printf("can't get fb_fix_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大小
   screen_size = var1.line_length*var.yres ; //整个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);

   //显示十字
   for(i = 0;i<20;i++)
   {
      lcd_show_pixel_rgb565(var.xres/2+i,var.yres/2,RED);
      lcd_show_pixel_rgb565(var.xres/2-i,var.yres/2,RED);
      lcd_show_pixel_rgb565(var.xres/2,var.yres/2+i,RED);
      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;
}

//画点函数
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));
   void *pen = fb_base + var1.line_length *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;
   }
}

程序现象也是相对简单,红色、绿色、蓝色、白色各显示一遍之后,最后在屏幕中显示一个十字。

14.5. 修改lcd相关设备树

野火STM32MP157提供了很多的设备树插件源码,用户只需要根据相关相对应的设备树插件修改即可。

野火设备树插件GitHub

如果修改了LCD显示屏的引脚,需要在这里修改对应的引脚。

ebf_linux_kernel/arch/arm/boot/dts/overlays/stm-fire-lcd-overlay.dts
 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
fragment@3 {
   target=<&pinctrl>;
   __overlay__{
      lcd_backlight_pins_a: lcd-backlight {
         pins {
            pinmux = <STM32_PINMUX('A', 15, AF1)>; /* TIM2_CH1 */
            bias-pull-down;
            drive-push-pull;
            slew-rate = <0>;
         };
      };

      lcd_backlight_sleep_pins_a: lcd-backlight-sleep {
         pins {
            pinmux = <STM32_PINMUX('A', 15, ANALOG)>; /* TIM2_CH1 */
         };
      };

      ltdc_pins_a: ltdc-a-0 {
         pins {
            pinmux = <STM32_PINMUX('G',  7, AF14)>, /* LCD_CLK */
                  <STM32_PINMUX('I', 10, AF14)>, /* LCD_HSYNC */
                  <STM32_PINMUX('I',  9, AF14)>, /* LCD_VSYNC */
                  <STM32_PINMUX('E', 13, AF14)>, /* LCD_DE */
                  <STM32_PINMUX('H',  2, AF14)>, /* LCD_R0 */
                  <STM32_PINMUX('H',  3, AF14)>, /* LCD_R1 */
                  <STM32_PINMUX('H',  8, AF14)>, /* LCD_R2 */
                  <STM32_PINMUX('B',  0, AF9)>, /* LCD_R3 */
                  <STM32_PINMUX('A', 5, AF14)>, /* LCD_R4 */
                  <STM32_PINMUX('C',  0, AF14)>, /* LCD_R5 */
                  <STM32_PINMUX('H', 12, AF14)>, /* LCD_R6 */
                  <STM32_PINMUX('E', 15, AF14)>, /* LCD_R7 */
                  <STM32_PINMUX('E', 14, AF13)>, /* LCD_G0 */
                  <STM32_PINMUX('E',  6, AF14)>, /* LCD_G1 */
                  <STM32_PINMUX('H', 13, AF14)>, /* LCD_G2 */
                  <STM32_PINMUX('H', 14, AF14)>, /* LCD_G3 */
                  <STM32_PINMUX('H', 15, AF14)>, /* LCD_G4 */
                  <STM32_PINMUX('I',  0, AF14)>, /* LCD_G5 */
                  <STM32_PINMUX('I',  1, AF14)>, /* LCD_G6 */
                  <STM32_PINMUX('I',  2, AF14)>, /* LCD_G7 */
                  <STM32_PINMUX('D',  9, AF14)>, /* LCD_B0 */
                  <STM32_PINMUX('G', 12, AF14)>, /* LCD_B1 */
                  <STM32_PINMUX('G', 10, AF14)>, /* LCD_B2 */
                  <STM32_PINMUX('D', 10, AF14)>, /* LCD_B3 */
                  <STM32_PINMUX('I',  4, AF14)>, /* LCD_B4 */
                  <STM32_PINMUX('A',  3, AF14)>, /* LCD_B5 */
                  <STM32_PINMUX('B',  8, AF14)>, /* LCD_B6 */
                  <STM32_PINMUX('D',  8, AF14)>; /* LCD_B7 */
            bias-disable;
            drive-push-pull;
            slew-rate = <1>;
         };
      };

      ltdc_pins_sleep_a: ltdc-a-1 {
         pins {
            pinmux = <STM32_PINMUX('G',  7, ANALOG)>, /* LCD_CLK */
                  <STM32_PINMUX('I', 10, ANALOG)>, /* LCD_HSYNC */
                  <STM32_PINMUX('I',  9, ANALOG)>, /* LCD_VSYNC */
                  <STM32_PINMUX('E', 13, ANALOG)>, /* LCD_DE */
                  <STM32_PINMUX('H',  2, ANALOG)>, /* LCD_R0 */
                  <STM32_PINMUX('H',  3, ANALOG)>, /* LCD_R1 */
                  <STM32_PINMUX('H',  8, ANALOG)>, /* LCD_R2 */
                  <STM32_PINMUX('B',  0, ANALOG)>, /* LCD_R3 */
                  <STM32_PINMUX('A',  5, ANALOG)>, /* LCD_R4 */
                  <STM32_PINMUX('C',  0, ANALOG)>, /* LCD_R5 */
                  <STM32_PINMUX('H', 12, ANALOG)>, /* LCD_R6 */
                  <STM32_PINMUX('E', 15, ANALOG)>, /* LCD_R7 */
                  <STM32_PINMUX('E', 14, ANALOG)>, /* LCD_G0 */
                  <STM32_PINMUX('E',  6, ANALOG)>, /* LCD_G1 */
                  <STM32_PINMUX('H', 13, ANALOG)>, /* LCD_G2 */
                  <STM32_PINMUX('H', 14, ANALOG)>, /* LCD_G3 */
                  <STM32_PINMUX('H', 15, ANALOG)>, /* LCD_G4 */
                  <STM32_PINMUX('I',  0, ANALOG)>, /* LCD_G5 */
                  <STM32_PINMUX('I',  1, ANALOG)>, /* LCD_G6 */
                  <STM32_PINMUX('I',  2, ANALOG)>, /* LCD_G7 */
                  <STM32_PINMUX('D',  9, ANALOG)>, /* LCD_B0 */
                  <STM32_PINMUX('G', 12, ANALOG)>, /* LCD_B1 */
                  <STM32_PINMUX('G', 10, ANALOG)>, /* LCD_B2 */
                  <STM32_PINMUX('D', 10, ANALOG)>, /* LCD_B3 */
                  <STM32_PINMUX('I',  4, ANALOG)>, /* LCD_B4 */
                  <STM32_PINMUX('A',  3, ANALOG)>, /* LCD_B5 */
                  <STM32_PINMUX('B',  8, ANALOG)>, /* LCD_B6 */
                  <STM32_PINMUX('D',  8, ANALOG)>; /* LCD_B7 */
         };
      };

   };
};

背光引脚被复用为PWM1的输出,如果修改了背光引脚也要在这里修改使用的pwm。

ebf_linux_kernel/arch/arm/boot/dts/overlays/stm-fire-lcd-overlay.dts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fragment@1 {
   target-path="/";
   __overlay__{
      lcd-backlight {
         compatible = "pwm-backlight";
         pwms = <&pwm2 0 1000000>;
         brightness-levels = <0 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>;
         default-brightness-level = <90>;
         status = "okay";
      };
   };
};

通常情况我们参考官方开发板设计硬件LCD使用的引脚和官方一致即可。 我们经常要修改的是LCD一些配置参数,例如分辨率、时钟、无效行数。 此处内容要根据自己使用的显示屏说明文档配置。

ebf_linux_kernel/arch/arm/boot/dts/overlays/stm-fire-lcd-overlay.dts
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
clock-frequency = <27000000>;
hactive = <800>;
vactive = <480>;
hfront-porch = <22>;
hback-porch = <46>;
hsync-len = <1>;
vback-porch = <23>;
vfront-porch = <22>;
vsync-len = <1>;
vrefresh = <60>;
bits-per-pixel_0=<24>; //unused

14.6. fire-config修改液晶参数

在终端执行sudo fire-config命令,选择“Device”项,如下图: (imx6ull和stm32mp157的fire-config界面基本相同)

fire-config01

选择“LCD”项

fire-config02

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

fire-config03

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

fire-config04