前言
最近看了 电子量产工具 这个项目,本专栏是对该项目的一个总结。
一、显示系统分析
可以看到上面的框图,我们可以分为三大部分:
- 从最下面的 第 1 层开始。这是整个项目的最底层,主要负责处理数据和逻辑,与板子直接进行交互。
- 中间的是第 2 层 管理层。负责处理应用的中间件功能,它位于底层程序和用户界面之间,主要向上层程序提供所需的底层代码。
- 最上面的就是综合应用,用来实现用户所需的功能。
项目主要包含了 5 个方面,分别是 显示管理器,输入管理器,字体管理器,页面管理器,图片格式管理器。首先实现显示系统功能。
可以看到 显示管理器 下面有两种显示输出的方式:Framebuffer 和 web输出。这里只着重讲解 Framebuffer 的显示实现。
在编写显示系统之前需要对 Framebuffer 有一定的了解,如何不了解的可以参考我之前的文章:Linux 应用基础 Framebuffer应用编程
二、封装显示结构体
对于显示方式有两种,可以将功能封装在结构体中,提高代码的扩展性和灵活性。
- DispOpr 显示设备结构体
typedef struct DispOpr { char *name; int (*DeviceInit)(void); //显示设备初始化 int (*DeviceExit)(void); int (*GetBuffer)(PDispBuff ptDispBuff); //获取LCD的framebuffer int (*FlushRegion)(PRegion ptRegion, PDispBuff ptDispBuff); //将数据刷新到LCD struct DispOpr *ptNext; //指针,用于连接链表 }DispOpr, *PDispOpr;
- DispBuff 是一个结构体,用于封装LCD的相关信息,例如分辨率,像素等。
buff: 用来后面 存放起始地址。
typedef struct DispBuff { int iXres; //x方向的分辨率 int iYres; //y方向的分辨率 int iBpp; //bits_per_pixel,每个像素占多少位 char *buff; }DispBuff, *PDispBuff;
- Region 也是一个结构体,用来表示一个区域的左上角坐标,宽度以及高度。
typedef struct Region { int iLeftUpX; int iLeftUpY; int iWidth; int iHeigh; }Region, *PRegion;
三、底层 Framebuffer
- 在 framebuffer 里实现 DispOpr 结构体。
这里要设置 name, 因为用于显示的设备不止 framebuffer ,还有 web 输入。
中间管理层,会根据这里所设置的 name 来 按照需求 选择不同的显示设备。
- 在 FbDeviceInit 中完成 framebuffer 的初始化。
- FbGetBuffer负责向上层 管理器返回初始化后的 LCD 的framebuffer 。将 var 中数值取出 放入函数参数的结构体里,传递给上层。
注意:FbFlushRegion 可以将绘制好buff 刷新到 LCD 上。
但是,在 FbDeviceInit 函数中,我们已经返回LCD的framebuffer, 以后上层APP可以直接操作LCD, 可以不用 FbFlushRegion。
四、显示管理层
管理层 起着承上启下的作用。
首先,管理层 管理着不同的显示设备,我们可以将不同设备放入链表,然后根据 name
来选择。
- 先将设备注册进入链表。
g_DispDevs 是头指针,在 RegisterDisplay 函数
- 中,采用头添加的方法将设备加入链表。
- 然后根据 要设备名来找到相应的显示设备。找到设备后对其初始化。
底层的代码已经实现了相应功能,只需要调用即可。
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(); //调用底层 DeviceInit初始化 if (ret) { printf("DeviceInit err\n"); return -1; } ret = g_DispDefault->GetBuffer(&g_tDispBuff); //调用底层 GetBuffer获取LCD的framebuffer 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; }
- 为了方便上层 APP 使用 LCD 的 framebuffer,我们需要将 framebuffer 传递给上层。
五、测试程序
测试层程序用于验证和检查应用的正确性和稳定性,例如 单元测试。
测试在 LCD 上显示一个英文字符。
在 main 函数里调用 管理层的代码,来实现相应功能。
- 刷新LCD 的代码可以忽略不写,因为我们直接 返回 LCD 的 framebuffer , 以后上层APP可以直接操作 LCD , 可以不用 FbFlushRegion 。
- 也可以 malloc 返回一块无关的 buffer , 要使用 FbFlushRegion。
注意 :显示字符前,要实现描点函数。
pen_8 ,pen_16,pen_32 是起笔点,可以相互转化。
/* 描点函数 */ 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 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); } } } }
实验效果图:
总结
虽然 这种分层的写法看起来更复杂,但是对于代码的可以移植性会大大提高。