前言:
前面我们已经对这个项目的基本框架有了一个初步的了解与认识,要实现显示管理器与输入管理器,有输入有输出基本就实现这个项目的大部分功能了,首先我们先来做显示系统,对于上层系统为了让程序更好扩展,我们得添加一个显示管理器,在下面有各种设备,就比如有Framebufler和web输出。
一、数据结构抽象
我们添加的一个显示管理器中有Framebufler和web输出,对于俩个不同的设备我们需要抽象出同一个结构体类型。
1.使用场景
在上图中我们可以将其分为两层,上层要获得下层某个结构体,通过这个结构体中的函数来操作、绘制、刷新上层的界面。
所有我们需要定义一个统一的结构体DispOpr
2.disp_manager.h
#ifndef _DISP_MANAGER_H #define _DISP_MANAGER_H typedef struct Region { int iLeftUpX; //左上角x坐标 int iLeftUpY; //左上角y坐标 int iWidth; //宽度 int iHeigh; //高度 }Region, *PRegion; typedef struct DispOpr { char *name; //显示模块的名字 char *GetBuffer(int *pXres, int *pYres, int *pBpp);//分辨率(长和宽)每个像素占据多少位 int FlushRegion(PRegion ptRegion, char *buffer);//刷出某个区域 struct DispOpr *ptNext; //链表 }; #endif
第1~2行:防止头文件在.c文件中多次定义
第4~9行:定义刷新区域结构体
第11~16行:定义统一管理的结构体
二、Framebuffer编程
1.disp_manager.h
#ifndef _DISP_MANAGER_H #define _DISP_MANAGER_H typedef struct Region { int iLeftUpX; int iLeftUpY; int iWidth; int iHeigh; }Region, *PRegion; typedef struct DispOpr { char *name; int DeviceInit(void); int DeviceExit(void); char *GetBuffer(int *pXres, int *pYres, int *pBpp); int FlushRegion(PRegion ptRegion, char *buffer); struct DispOpr *ptNext; }DispOpr, *PDispOpr; #endif
第13行:初始化函数定义
第14行:退出函数定义
2.framebuffer.c
#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" static int fd_fb; //framebuffer文件 static struct fb_var_screeninfo var; /* Current var */ static int screen_size; //framebuffer长度 static unsigned char *fb_base; //framebuffer地址 static unsigned int line_width; static unsigned int pixel_width; static int DeviceInit(void) { fd_fb = open("/dev/fb0", O_RDWR); if (fd_fb < 0) { printf("can't open /dev/fb0\n"); return -1; } if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) { printf("can't get var\n"); return -1; } 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; fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0); if (fb_base == (unsigned char *)-1) { printf("can't mmap\n"); return -1; } return 0; } static int DeviceExit(void) { munmap(fb_base, screen_size); close(fd_fb); return 0; } /* 可以返回LCD的framebuffer, 以后上层APP可以直接操作LCD, 可以不用FbFlushRegion * 也可以malloc返回一块无关的buffer, 要使用FbFlushRegion刷到LCD上 */ static int FbGetBuffer(PDispBuff ptDispBuff); { ptDispBuff->iXres = var.xres; ptDispBuff->iYres = var.yres; ptDispBuff->iBpp = var.bits_per_pixel; ptDispBuff->buff = (char *)fb_base; return 0; } static int FbFlushRegion(PRegion ptRegion, PDispBuff ptDispBuff) { return 0; } static DispOpr g_tFramebufferOpr = { .name = "fb", .DeviceInit = FbDeviceInit, .DeviceExit = FbDeviceExit, .GetBuffer = FbGetBuffer, .FlushRegion = FbFlushRegion, }; void FramebufferInit(void) { RegisterDisplay(&g_tFramebufferOpr); }
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
#ifndef _DISP_MANAGER_H #define _DISP_MANAGER_H #ifndef NULL #define NULL (void *)0 #endif typedef struct DispBuff { int iXres; //x坐标分辨率 int iYres; //y坐标分辨率 int iBpp; //bpp char *buff; 缓冲区地址 }DispBuff, *PDispBuff; typedef struct Region { int iLeftUpX; int iLeftUpY; int iWidth; int iHeigh; }Region, *PRegion; typedef struct DispOpr { char *name; int (*DeviceInit)(void); int (*DeviceExit)(void); int (*GetBuffer)(PDispBuff ptDispBuff); int (*FlushRegion)(PRegion ptRegion, PDispBuff ptDispBuff); struct DispOpr *ptNext; }DispOpr, *PDispOpr; void RegisterDisplay(PDispOpr ptDispOpr); void DisplayInit(void); int SelectDefaultDisplay(char *name); int InitDefaultDisplay(void); int PutPixel(int x, int y, unsigned int dwColor); int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff); PDispBuff GetDisplayBuffer(void); #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
#include <disp_manager.h> #include <stdio.h> #include <string.h> /* 管理底层的LCD、WEB */ static PDispOpr g_DispDevs = NULL; static PDispOpr g_DispDefault = NULL; static DispBuff g_tDispBuff; static int line_width; static int pixel_width; int PutPixel(int x, int y, unsigned int dwColor) { unsigned char *pen_8 = (*unsigned char*)(g_tDispBuff.buff+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 (g_tDispBuff.iBpp) { case 8: { *pen_8 = dwColor; break; } case 16: { /* 565 */ red = (dwColor >> 16) & 0xff; green = (dwColor >> 8) & 0xff; blue = (dwColor >> 0) & 0xff; dwColor = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3); *pen_16 = dwColor; break; } case 32: { *pen_32 = dwColor; break; } default: { printf("can't surport %dbpp\n", g_tDispBuff.iBpp); return -1; break; } } return 0; } void RegisterDisplay(PDispOpr ptDispOpr) { ptDispOpr->ptNext = g_DispDevs; g_DispDevs = ptDispOpr; } int SelectDefaultDisplay(char *name) { PDispOpr pTmp = g_DispDevs; while (pTmp) { if (strcmp(name, pTmp->name) == 0) { g_DispDefault = pTmp; return 0; } pTmp = pTmp->ptNext; } return -1; } int InitDefaultDisplay(void) { int ret; ret = g_DispDefault->DeviceInit(); if (ret) { printf("DeviceInit err\n"); return -1; } ret = g_DispDefault->GetBuffer(&g_tDispBuff); if (ret) { printf("GetBuffer err\n"); return -1; } line_width = g_tDispBuff.iXres * g_tDispBuff.iBpp/8; pixel_width = g_tDispBuff.iBpp/8; return 0; } int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff) { return g_DispDefault->FlushRegion(ptRegion, ptDispBuff); } void DisplayInit(void) { void FramebufferInit(void); FramebufferInit(); } PDispBuff GetDisplayBuffer(void) { return &g_tDispBuff; }
第6行:将底层的LCD、WEB所实现的结构体放入到链表(指针)中,所以我们需要提供一个函数。void RegisterDisplay(PDispOpr ptDispOpr)
第7行:存放找到名字的新链表
第8行:定义一个全局变量 g_tDispBuff之后用来放第89行中GetBuffer中的(int* pxres, int * pYres, int * pBpp)
void RegisterDisplay(PDispOpr ptDispOpr)
第57~61行:构造注册函数,将typedef struct DispOpr这个结构体注册到g_DispDevs链表中
第59行:传入底层结构体指针ptDispOpr指向链表头g_DispDevs
第60行:链表头g_DispDevs指向ptDispOpr
void DisplayInit(void)
第113~117行:调用底层提供的 FramebufferInit();目前我们只需要调用FramebufferInit()即可,还没有实现WEB的功能
如果g_DispDevs链表中有好几个模块,那我们应该如何选择呢,现在我们就要设计一个模块选择函数进行模块的选择
int SelectDefaultDisplay(char *name)
第60~75行:模块选择函数
定义一个临时指针pTmp存放链表头
while循环里根据名字name找到那一项
找到的名字放到新链表g_DispDefault中,在第五行中有定义
第75行,如果这个不对,这找寻下一个是否正确
因为上层需要通过中间的显示管理来绘制图像,坐标反馈给底层进行反应,所有需要在中间层加入绘制函数
int PutPixel(int x, int y, unsigned int dwColor)
第9~10:每个像素占据多长多少像素可以先定义好,事先计算好,每一行多少字节,每个像素多少宽度,这个在InitDefaultDisplay()中已经计算好了
第12~53行:设置绘制图像函数
函数内表示绘制的x、y坐标和绘制图像的颜色
第14行:g_tDispBuff.buff是写存的基地址
第23行:g_tDispBuff.iBpp是每个像素的宽度
想要在上层显示一个像素,首先需要得到一块内存,内存可以调用到底层提供的结构体,里面的g_tDispBuff这个函数,在上层得到一个新的buf,在这块buf里面绘制图像,但是g_tDispBuff不可以放到PutPixel,我们需要再定义一个函数: InitDefaultDisplay(void)
选择默认的SelectDefaultDisplay后,我们还需要初始化
int InitDefaultDisplay(void)
第81~104行:
第85行: 调用 g_DispDefault 中的DeviceInit()
第93行: 调用 g_DispDefault 中的GetBuffer(&g_tDispBuff)
这里我们需要定义一个新的GetBuffer结构体,把它的(int* pxres, int * pYres, int * pBpp)再次进行封装,封装结构体在disp_manager.h中的DispBuff
第93行:将GetBuffer中的(int* pxres, int * pYres, int * pBpp)放到全局变量 g_tDispBuff中
第100行:line_width表示每一行占据多少个字节
第101行:pixel_width表示每个像素的宽度
当我们上层已经通过 PutPixel 已经绘制好图像,那我们就需要把它刷到硬件上,我们需要再提供一个函数FlushDisplayRegion
int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff)
第109行:调用底层设备提供的FlushRegion函数
返回取址
PDispBuff GetDisplayBuffer(void)
献上韦东山老师对以上代码流程的梳理过程:
电子产品量产工具显示系统代码流程
四、测试单元
book@100ask:~/04_disp_unittest/display$ vi Makefile
1.通用Makefile
CROSS_COMPILE ?= AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld CC = $(CROSS_COMPILE)gcc CPP = $(CC) -E AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nm STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump export AS LD CC CPP AR NM export STRIP OBJCOPY OBJDUMP CFLAGS := -Wall -O2 -g CFLAGS += -I $(shell pwd)/include LDFLAGS := export CFLAGS LDFLAGS TOPDIR := $(shell pwd) export TOPDIR TARGET := test obj-y += display/ obj-y += unittest/ all : start_recursive_build $(TARGET) @echo $(TARGET) has been built! start_recursive_build: make -C ./ -f $(TOPDIR)/Makefile.build $(TARGET) : built-in.o $(CC) -o $(TARGET) built-in.o $(LDFLAGS) clean: rm -f $(shell find -name "*.o") rm -f $(TARGET) distclean: rm -f $(shell find -name "*.o") rm -f $(shell find -name "*.d") rm -f $(TARGET)
第27行:编译出test的应用程序
第30行:指定display下的目录
第31行:指定unittest下的目录
2.display下的Makefile
EXTRA_CFLAGS := CFLAGS_file.o := obj-y += disp_manager.o obj-y += framebuffer.o
3. unittest下的Makefile
EXTRA_CFLAGS := CFLAGS_file.o := 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; }
五、上板测试:
1.ubuntu上进行make编译:
2.开发板进行配置
对于 IMX6ULL,首先需要关闭默认的 qt gui 程序,才可以执行 ts_test_mt 测试命令,关闭 qt 命令如下所示:
[root@100ask:/etc/init.d]# mv S99myirhmi2 /root [root@100ask:/etc/init.d]# reboot
如果遇到移除开发板GUI程序后,仍显示Poky界面。
解决方法
在imx6ull系统 /etc/init.d 目录下,搜索psplash 关键字,看在那个文件有用到,有用到就把它注释掉。
可以看到:
在/etc/init.d/S50sshd的12和15行有使用,在该行前面加#号注释。
在/etc/init.d/rcS的7行有使用,在该行前面加#号注释。
然后重启开发板
poky界面消失。
挂载网络文件系统
[root@100ask:~]# mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
执行./test最终效果:在开发板上打印出字符A,到此,显示管理系统代码完成