前言:
前面我们已经对这个项目的基本框架有了一个初步的了解与认识,要实现显示管理器与输入管理器,有输入有输出基本就实现这个项目的大部分功能了,首先我们先来做显示系统,对于上层系统为了让程序更好扩展,我们得添加一个显示管理器,在下面有各种设备,就比如有Framebufler和web输出。
一、数据结构抽象
我们添加的一个显示管理器中有Framebufler和web输出,对于俩个不同的设备我们需要抽象出同一个结构体类型。
1.使用场景

在上图中我们可以将其分为两层,上层要获得下层某个结构体,通过这个结构体中的函数来操作、绘制、刷新上层的界面。
所有我们需要定义一个统一的结构体DispOpr
2.disp_manager.h
  1 #ifndef _DISP_MANAGER_H
  2 #define _DISP_MANAGER_H
  3
  4 typedef struct Region {
  5     int iLeftUpX;                //左上角x坐标
  6     int iLeftUpY;                //左上角y坐标
  7     int iWidth;                  //宽度
  8     int iHeigh;                  //高度
  9 }Region, *PRegion;
 10
 11 typedef struct DispOpr {
 12     char *name;                  //显示模块的名字 
 13     char *GetBuffer(int *pXres, int *pYres, int *pBpp);//分辨率(长和宽)每个像素占据多少位
 14     int FlushRegion(PRegion ptRegion, char *buffer);//刷出某个区域
 15     struct DispOpr *ptNext;      //链表
 16 };
 17
 18 #endif
 19第1~2行:防止头文件在.c文件中多次定义
第4~9行:定义刷新区域结构体
第11~16行:定义统一管理的结构体
二、Framebuffer编程
1.disp_manager.h 
 
  1 #ifndef _DISP_MANAGER_H
  2 #define _DISP_MANAGER_H
  3
  4 typedef struct Region {
  5     int iLeftUpX;
  6     int iLeftUpY;
  7     int iWidth;
  8     int iHeigh;
  9 }Region, *PRegion;
 10
 11 typedef struct DispOpr {
 12     char *name;
 13     int DeviceInit(void);
 14     int DeviceExit(void);
 15     char *GetBuffer(int *pXres, int *pYres, int *pBpp);
 16     int FlushRegion(PRegion ptRegion, char *buffer);
 17     struct DispOpr *ptNext;
 18 }DispOpr, *PDispOpr;
 19
 20 #endif
第13行:初始化函数定义
第14行:退出函数定义
2.framebuffer.c
  1 #include <sys/mman.h>
  2 #include <sys/types.h>
  3 #include <sys/stat.h>
  4 #include <unistd.h>
  5 #include <linux/fb.h>
  6 #include <fcntl.h>
  7 #include <stdio.h>
  8 #include <string.h>
  9 #include <sys/ioctl.h>
 10
 11 #include "disp_manager.h"
 12
 13 static int fd_fb;                       //framebuffer文件
 14 static struct fb_var_screeninfo var;    /* Current var */
 15 static int screen_size;                 //framebuffer长度
 16 static unsigned char *fb_base;          //framebuffer地址
 17 static unsigned int line_width;
 18 static unsigned int pixel_width;
 19
 20 static int DeviceInit(void)
 21 {
 22     fd_fb = open("/dev/fb0", O_RDWR);
 23     if (fd_fb < 0)
 24     {
 25         printf("can't open /dev/fb0\n");
 26         return -1;
 27     }
 28     if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
 29     {
 30         printf("can't get var\n");
 31         return -1;
 32     }
 33
 34     line_width  = var.xres * var.bits_per_pixel / 8;
 35     pixel_width = var.bits_per_pixel / 8;
 36     screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
 37     fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
 38     if (fb_base == (unsigned char *)-1)
 39     {
 40         printf("can't mmap\n");
 41         return -1;
 42     }
 43
 44     return 0;
 45 }
 46
 47 static int DeviceExit(void)
 48 {
 49     munmap(fb_base, screen_size);
 50     close(fd_fb);
 51     return 0;
 52 }
 53
 54
 55 /* 可以返回LCD的framebuffer, 以后上层APP可以直接操作LCD, 可以不用FbFlushRegion
 56  * 也可以malloc返回一块无关的buffer, 要使用FbFlushRegion刷到LCD上
 57  */
 58 static int FbGetBuffer(PDispBuff ptDispBuff);
 59
 60 {
 61     ptDispBuff->iXres = var.xres;
 62     ptDispBuff->iYres = var.yres;
 63     ptDispBuff->iBpp  = var.bits_per_pixel;
 64     ptDispBuff->buff  = (char *)fb_base;
 65     return 0;
 66 }
 67
 68 static int FbFlushRegion(PRegion ptRegion, PDispBuff ptDispBuff)
 69 {
 70     return 0;
 71 }
 72
 73
 74 static DispOpr g_tFramebufferOpr = {
 75     .name        = "fb",
 76     .DeviceInit  = FbDeviceInit,
 77     .DeviceExit  = FbDeviceExit,
 78     .GetBuffer   = FbGetBuffer,
 79     .FlushRegion = FbFlushRegion,
 80 };
 81
 82
 83 void FramebufferInit(void)
 84 {
 85     RegisterDisplay(&g_tFramebufferOpr);
 86 }
 87
20 static int DeviceInit(void)第20~45行:初始化函数
47 static int DeviceExit(void)第47~52行:退出函数
第58~66行的FbGetBuffer是已经封装好的,详细封装过程后续会讲
第61~62行:设置分辨率
第63行:设置bpp
74 static DispOpr g_tFramebufferOpr第74~80行:上层代码可以根据这个结构体里的函数来初始化LCD来得到Buffer
第78行:通过.GetBuffer = FbGetBuffer来构建好图像和文字
第79行:通过.FlushRegion = FbFlushRegion来刷新到LCD上
第83~86行:注册结构体g_tFramebufferOpr,详细介绍在三、显示管理
三、显示管理
上层函数想要选择哪个设备进行显示,需要中间加一个函数进行选择,起到承上启下的作用,用来实现显示管理,是操作Framebuffer还是WEB设备,需要进行选择某个模块,好可以提供一些函数,描点等

1.disp_manager.h
  1 #ifndef _DISP_MANAGER_H
  2 #define _DISP_MANAGER_H
  3
  4 #ifndef NULL
  5 #define NULL (void *)0
  6 #endif
  7
  8 typedef struct DispBuff {
  9     int iXres;     //x坐标分辨率
 10     int iYres;     //y坐标分辨率
 11     int iBpp;      //bpp
 12     char *buff;    缓冲区地址
 13 }DispBuff, *PDispBuff;
 14
 15
 16 typedef struct Region {
 17     int iLeftUpX;
 18     int iLeftUpY;
 19     int iWidth;
 20     int iHeigh;
 21 }Region, *PRegion;
 22
 23 typedef struct DispOpr {
 24     char *name;
 25     int (*DeviceInit)(void);
 26     int (*DeviceExit)(void);
 27     int (*GetBuffer)(PDispBuff ptDispBuff);
 28     int (*FlushRegion)(PRegion ptRegion, PDispBuff ptDispBuff);
 29     struct DispOpr *ptNext;
 30 }DispOpr, *PDispOpr;
 31
 32 void RegisterDisplay(PDispOpr ptDispOpr);
 33
 34 void DisplayInit(void);
 35 int SelectDefaultDisplay(char *name);
 36 int InitDefaultDisplay(void);
 37 int PutPixel(int x, int y, unsigned int dwColor);
 38 int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff);
 39 PDispBuff GetDisplayBuffer(void);
 40
 41
 42 #endif
第8~13行:进一步封装GetBuffer(int *pXres, int *pYres, int *pBpp)
第27行:将4~9行的PDispBuff保存到GetBuffer中
第32行:定义注册函数RegisterDisplay
第34行:调用底层提供的模块
第35行:定义选择模块函数
第36行:选择默认的SelectDefaultDisplay后,我们还需要定义初始化函数
第37行:提供绘制图像函数
第38行:将绘制好的图像刷新到硬件上
第39行:返回取址
2.disp_manager.c
  1 #include <disp_manager.h>
  2 #include <stdio.h>
  2 #include <string.h>
  2
  3 /* 管理底层的LCD、WEB */
  4 static PDispOpr g_DispDevs = NULL;
  5 static PDispOpr g_DispDefault = NULL;
  6 static DispBuff g_tDispBuff;
  7 static int line_width;
  8 static int pixel_width;
  9
 10 int PutPixel(int x, int y, unsigned int dwColor)
 11 {
 12     unsigned char *pen_8 = (*unsigned char*)(g_tDispBuff.buff+y*line_width+x*pixel_width);
 13     unsigned short *pen_16;
 14     unsigned int *pen_32;
 15
 16     unsigned int red, green, blue;
 17
 18     pen_16 = (unsigned short *)pen_8;
 19     pen_32 = (unsigned int *)pen_8;
 20
 21     switch (g_tDispBuff.iBpp)
 22     {
 23         case 8:
 24         {
 25             *pen_8 = dwColor;
 26             break;
 27         }
 28         case 16:
 29         {
 30             /* 565 */
 31             red   = (dwColor >> 16) & 0xff;
 32             green = (dwColor >> 8) & 0xff;
 33             blue  = (dwColor >> 0) & 0xff;
 34             dwColor = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
 35             *pen_16 = dwColor;
 36             break;
 37         }
 38         case 32:
 39         {
 40             *pen_32 = dwColor;
 41             break;
 42         }
 43         default:
 44         {
 45             printf("can't surport %dbpp\n", g_tDispBuff.iBpp);
 45             return -1;
 46             break;
 47         }
 48     }
 48      return 0;
 49 }
 50
 51
 52
 53 void RegisterDisplay(PDispOpr ptDispOpr)
 54 {
 55     ptDispOpr->ptNext = g_DispDevs;
 56     g_DispDevs = ptDispOpr;
 57 }
 58
 59
 60 int SelectDefaultDisplay(char *name)
 61 {
 62     PDispOpr pTmp = g_DispDevs;
 63     while (pTmp)
 64     {
 65         if (strcmp(name, pTmp->name) == 0)
 66         {
 67             g_DispDefault = pTmp;
 68             return 0;
 69         }
 70
 71         pTmp = pTmp->ptNext;
 72     }
 73
 74     return -1;
 75 }
 76
 77 int InitDefaultDisplay(void)
 78 {
 79     int ret;
 80
 81     ret = g_DispDefault->DeviceInit();
 82     if (ret)
 83     {
 84         printf("DeviceInit err\n");
 85         return -1;
 86     }
 87
 88
 89     ret = g_DispDefault->GetBuffer(&g_tDispBuff);
 90     if (ret)
 91     {
 92         printf("GetBuffer err\n");
 93         return -1;
 94     }
 95
 96     line_width  = g_tDispBuff.iXres * g_tDispBuff.iBpp/8;
 97     pixel_width = g_tDispBuff.iBpp/8;
 98
 99     return 0;
100 }
101
102
103 int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff)
104 {
105     return g_DispDefault->FlushRegion(ptRegion, ptDispBuff);
106 }
107
108
109 void DisplayInit(void)
110 {
111     void FramebufferInit(void);
112     FramebufferInit();
113 }
114
115 PDispBuff GetDisplayBuffer(void)
116 {
117    return &g_tDispBuff;
118 }
第4行:将底层的LCD、WEB所实现的结构体放入到链表(指针)中,所以我们需要提供一个函数。void RegisterDisplay(PDispOpr ptDispOpr)
第5行:存放找到名字的新链表
第6行:定义一个全局变量 g_tDispBuff之后用来放第89行中GetBuffer中的(int* pxres, int * pYres, int * pBpp)
53 void RegisterDisplay(PDispOpr ptDispOpr)第53~57行:构造注册函数,将typedef struct DispOpr这个结构体注册到g_DispDevs链表中
第55行:传入底层结构体指针ptDispOpr指向链表头g_DispDevs
第56行:链表头g_DispDevs指向ptDispOpr
109 void DisplayInit(void)第109~112行:调用底层提供的 FramebufferInit();目前我们只需要调用FramebufferInit()即可,还没有实现WEB的功能
如果g_DispDevs链表中有好几个模块,那我们应该如何选择呢,现在我们就要设计一个模块选择函数进行模块的选择
60 int SelectDefaultDisplay(char *name)第60~75行:模块选择函数
定义一个临时指针pTmp存放链表头
while循环里根据名字name找到那一项
找到的名字放到新链表g_DispDefault中,在第五行中有定义
第71行,如果这个不对,这找寻下一个是否正确
因为上层需要通过中间的显示管理来绘制图像,坐标反馈给底层进行反应,所有需要在中间层加入绘制函数
10 int PutPixel(int x, int y, unsigned int dwColor)第7~8行:每个像素占据多长多少像素可以先定义好,事先计算好,每一行多少字节,每个像素多少宽度,这个在InitDefaultDisplay()中已经计算好了
第10~49行:设置绘制图像函数
函数内表示绘制的x、y坐标和绘制图像的颜色
第12行:g_tDispBuff.buff是写存的基地址
第21行:g_tDispBuff.iBpp是每个像素的宽度
想要在上层显示一个像素,首先需要得到一块内存,内存可以调用到底层提供的结构体,里面的g_tDispBuff这个函数,在上层得到一个新的buf,在这块buf里面绘制图像,但是g_tDispBuff不可以放到PutPixel,我们需要再定义一个函数: InitDefaultDisplay(void)
选择默认的SelectDefaultDisplay后,我们还需要初始化
77 int InitDefaultDisplay(void)第77~100行:
第81行: 调用 g_DispDefault 中的DeviceInit()
第89行: 调用 g_DispDefault 中的GetBuffer(&g_tDispBuff)
这里我们需要定义一个新的GetBuffer结构体,把它的(int* pxres, int * pYres, int * pBpp)再次进行封装,封装结构体在disp_manager.h中的DispBuff
第89行:将GetBuffer中的(int* pxres, int * pYres, int * pBpp)放到全局变量 g_tDispBuff中
第96行:line_width表示每一行占据多少个字节
第97行:pixel_width表示每个像素的宽度
当我们上层已经通过 PutPixel 已经绘制好图像,那我们就需要把它刷到硬件上,我们需要再提供一个函数FlushDisplayRegion
103 int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff)第105行:调用底层设备提供的FlushRegion函数
返回取址
114 PDispBuff GetDisplayBuffer(void)
献上韦东山老师对以上代码流程的梳理过程:
电子产品量产工具显示系统代码流程
四、测试单元

book@100ask:~/04_disp_unittest/display$ vi Makefile
1.通用Makefile
  1
  2 CROSS_COMPILE ?=
  3 AS      = $(CROSS_COMPILE)as
  4 LD      = $(CROSS_COMPILE)ld
  5 CC      = $(CROSS_COMPILE)gcc
  6 CPP     = $(CC) -E
  7 AR      = $(CROSS_COMPILE)ar
  8 NM      = $(CROSS_COMPILE)nm
  9
 10 STRIP       = $(CROSS_COMPILE)strip
 11 OBJCOPY     = $(CROSS_COMPILE)objcopy
 12 OBJDUMP     = $(CROSS_COMPILE)objdump
 13
 14 export AS LD CC CPP AR NM
 15 export STRIP OBJCOPY OBJDUMP
 16
 17 CFLAGS := -Wall -O2 -g
 18 CFLAGS += -I $(shell pwd)/include
 19
 20 LDFLAGS :=
 21
 22 export CFLAGS LDFLAGS
 23
 24 TOPDIR := $(shell pwd)
 25 export TOPDIR
 26
 27 TARGET := test
 28
 29
 30 obj-y += display/
 31 obj-y += unittest/
 32
 33 all : start_recursive_build $(TARGET)
 34     @echo $(TARGET) has been built!
 35
 36 start_recursive_build:
 37     make -C ./ -f $(TOPDIR)/Makefile.build
 38
 39 $(TARGET) : built-in.o
 40     $(CC) -o $(TARGET) built-in.o $(LDFLAGS)
 41
 42 clean:
 43     rm -f $(shell find -name "*.o")
 44     rm -f $(TARGET)
 45
 46 distclean:
 47     rm -f $(shell find -name "*.o")
 48     rm -f $(shell find -name "*.d")
 49     rm -f $(TARGET)
 50
第27行:编译出test的应用程序
第30行:指定display下的目录
第31行:指定unittest下的目录
2.display下的Makefile
  1 EXTRA_CFLAGS  :=
  2 CFLAGS_file.o :=
  3
  4 obj-y += disp_manager.o
  5 obj-y += framebuffer.o
3. unittest下的Makefile
  1 EXTRA_CFLAGS  :=
  2 CFLAGS_file.o :=
  3
  4 obj-y += disp_test.o
4.
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fb.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <disp_manager.h>
#define FONTDATAMAX 4096
static const unsigned char fontdata_8x16[FONTDATAMAX] = {
    //字符编码,由于太多就不展示了
    .........
}
void lcd_put_ascii(int x, int y, unsigned char c)
{
	unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
	int i, b;
	unsigned char byte;
	for (i = 0; i < 16; i++)
	{
		byte = dots[i];
		for (b = 7; b >= 0; b--)
		{
			if (byte & (1<<b))
			{
				/* show */
				PutPixel(x+7-b, y+i, 0xffffff); /* 白 */
			}
			else
			{
				/* hide */
				PutPixel(x+7-b, y+i, 0); /* 黑 */
			}
		}
	}
}
int main(int argc, char **argv)
{
	Region region;                 //定义刷新区域的大小
	PDispBuff ptBuffer;
		
	DisplayInit();                 //调用DisplayInit()
	SelectDefaultDisplay("fb");    //选择默认的设备,传入一个名字为fd的设备
	InitDefaultDisplay();          //初始化这个默认的设备
	lcd_put_ascii(100, 100, 'A');  //在屏幕100,100的位置显示一个字母A
	region.iLeftUpX = 100;
	region.iLeftUpY = 100;
	region.iWidth   = 8;
	region.iHeigh   = 16;
	ptBuffer = GetDisplayBuffer();
	FlushDisplayRegion(®ion, ptBuffer);//将这块区域刷到这个硬件中
	
	return 0;	
}
五、上板测试:
将原有的qt程序停止
[root@100ask:~]# systemctl stop myir
最终效果:在开发板上打印出字符A







![P9231 [蓝桥杯 2023 省 A] 平方差(拆分问题)](https://img-blog.csdnimg.cn/a6ac5f5d71bc4f49b6134345af7ace98.png)










