总框图
显示管理:
我们需要在上层代码和底层代码之间抽象出一个管理层代码(disp_manager.c),它的作用如下
- 承上启下
- 底层将各个模块(LCD或Web DispOpr结构体)注册进disp_manager.c
- 上层APP选择使用哪个模块(LCD或Web)
- 可以提供一些函数,比如PutPixel(描点),然后将修改过的区域刷到LCD或Web上
disp_manager.c内容:
#include "disp_manager.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 = 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);
break;
}
}
}
void RegisterDisply(PDispOpr ptDispOpr)
{
ptDispOpr->ptNext = g_DispDevs; //ptNext指向链表头
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)
{
print("DeviceInit err\n");
return -1;
}
ret = g_DispDefault->GetBuffer(&g_tDispBuff);
{
print("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)
{
FramebufferInit();
}
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;
static struct fb_var_screeninfo var; /* Current var */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;
static int FbDeviceInit(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 FbDeviceExit(void)
{
int munmap(screen_size, screen_size); //解除映射关系
close(fd_fb);
return 0;
}
/*
*可以返回LCD的Framebuffer,以后上层APP可以直接操作LCD,可以不用FbFlushRegion
*也可以malloc返回一块无关的buffer,要使用FbFlushRegion
*/
static int FbGetBuffer(PDispBuff ptDispBuff)
{
ptDispBuff->iXres = var.xres;
ptDispBuff->iYres = var.yres;
ptDispBuff->iBpp = var.bits_per_pixel;
ptDispBuff->buff = fb_base;
return 0;
}
static int FbFlushRegion(PRegion ptRegion, PDispBuff ptDispBuff)
{
return 0;
}
/* 需要实现DispOpr结构体,结构体需要增加DeviceInit和DeviceExit两个函数 */
static DispOpr g_tFramebufferOpr = {
.name = "fb",
.DeviceInit = FbDeviceInit,
.DeviceExit = FbDeviceExit,
.GetBuffer = FbGetBuffer,
.FlushRegion = FbFlushRegion,
};
void FramebufferInit(void)
{
RegisterDisply(&g_tFramebufferOpr);
}
disp_manager.h 内容
#ifndef _DISP_MANAGER_H
#define _DISP_MANAGER_H
typedef struct DispBuff
{
int iXres;
int iYres;
int iBpp;
char *buff;
} DispBuff, *PDispBuff;
typedef struct Region
{
/* 某个按钮的区域需要以下内容表示*/
int iLeftUpX; //左上角X坐标
int iLeftUpY; //左上角Y坐标
int iWidth; //宽度
int iHeigh; //高度
} Region, *PRegion;
/* 上层代码调用DispOpr结构体函数就可以显示数据;此时可以通过拷贝数据到Framebuffer或者通过网络将数据发送至web浏览器 */
typedef struct DispOpr
{
char *name; //通过名字取得不同的结构体(比如:Framebuffer或者web输出)
int DeviceInit(void); //设备初始化
int DeviceExit(void); //设备退出
int GetBuffer(PDispBuff ptDispBuff); //绘制图片,需要获得一个buffer,返回内存的起始地址,还可以得到分辨率和每个像素的位数
int FlushRegion(PRegion ptRegion, PDispBuff ptDispBuff); //刷新区域,比如某个按钮的区域
struct DispOpr *ptNext; //可能支持多个输出设备(Freambuffer,或者web输出),需要链表将结构体链接起来
} DispOpr, *PDispOpr;
void RegisterDisply(PDispOpr ptDispOpr);
int SelectDefaultDisplay(char *name);
int InitDefaultDisplay(void);
void FramebufferInit(void);
void DisplayInit(void);
int PutPixel(int x, int y, unsigned int dwColor);
int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff);
#endif
程序调用流程
1、应用程序调用DisplayInit,DisplayInit调用底层FramebufferInit(也可以添加WebbufferInit)
2、底层FramebufferInit调用RegisterDisply 注册函数,将g_tFramebufferOpr结构体放入链表。
3、假设链表有很多项,对应不同的显示方式。此时调用SelectDefaultDisplay选择默认的显示设备。
4、选择默认的显示设备后,可以使用InitDefaultDisplay初始化显示设备
InitDefaultDisplay 调用g_DispDefault->DeviceInit和g_DispDefault->GetBuffer完成设备的初始化和获得buffer工作。
5、此时可以调用PutPixel绘制图片,然后调用FlushDisplayRegion将绘制好的区域刷到硬件上。
1、DisplayInit
->FramebufferInit
->RegisterDisply
2、SelectDefaultDisplay
3、InitDefaultDisplay
->g_DispDefault->DeviceInit
->g_DispDefault->GetBuffer
4、PutPixel
5、FlushDisplayRegion
总结:
程序虽然变复杂了,但是程序变的更加容易扩展,以后更换显示硬件,不需要改动disp_manager.c 这一层程序,只需要将底层的结构体实现即可,这便是程序分层的概念.。