目录
系统框图
见链接 https://kdocs.cn/l/ckwixdBIP6lf
头文件disp_manager.h
:定义与显示系统有关的结构体和函数
路径:“\source\01_display_struct\disp_manager.h”
宏定义空指针
#define NULL (void *)0
- 1
详细介绍见 http://iyenn.com/rec/1709682.html
这个NULL的宏定义后面在disp_manager.c
中会用到,就是下面的两句代码会运到:
static PDispOpr g_DispDevs = NULL;
static PDispOpr g_DispDefault = NULL;
- 1
- 2
结构体DispBuff
typedef struct DispBuff {
int iXres;
int iYres;
int iBpp;
char *buff;
}DispBuff, *PDispBuff;
- 1
- 2
- 3
- 4
- 5
- 6
DispBuff结构体用于存储设备返回的x方向的分辨率(iXres)、y方向的分辨率(iYres)、每个像素点用多少位表示(iBpp)、虚拟内存映射的基指针buff。
结构体Region
typedef struct Region {
int iLeftUpX;
int iLeftUpY;
int iWidth;
int iHeigh;
}Region, *PRegion;
- 1
- 2
- 3
- 4
- 5
- 6
这个结构体用于存储一个具体的区域,这个在区域刷新显示时会用到。
结构体struct DispOpr
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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这个结构体就是一个设备的底层信息的汇总结构体,是该显示系统中非常重要的结构体。
注意:成员DeviceInit、DeviceExit、GetBuffer、FlushRegion都是函数指针,以int (*DeviceInit)(void);
为例说明如下:
int:指向的函数返回一个 int 类型值。
(*DeviceInit):表明 DeviceInit 是一个函数指针。
(void):表示函数指针所指向的函数没有参数。
函数声明
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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
具体的这些函数的作用请见下面对各源文件中各函数的分析。
文件framebuffer.c
:Frambuffer设备的初始化和管理
需要的全局变量:
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;
- 1
- 2
- 3
- 4
- 5
- 6
前面的static表示这些变量仅限在本文件内使用,如果想深入了解这些变量,把博文 http://iyenn.com/rec/1709395.html 看一遍就行了。
下面是详细解释:
fd_fb:代表文件描述符。
var:这是 fb_var_screeninfo 结构体的指针,ioctl 函数通过此结构体返回获取到的显示设备信息。
screen_size:屏幕大小。
fb_base:将 /dev/fb0 设备的内存映射到进程的虚拟内存空间的地址;
line_width:LCD屏一行所占的字节数;
pixel_width:每个像素的字节宽度。
函数FbDeviceInit()
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;
}
- 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
这个函数实际上就是博文 IMX6ULL开发板基础实验:Framebuffer驱动程序的简单应用实例代码详细分析里的代码,主要作用就是打开Framebuffer设备文件,获得文件描述符,并映射到程序的虚拟内存。
函数FbDeviceExit()
static int FbDeviceExit(void)
{
munmap(fb_base, screen_size);
close(fd_fb);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
这个函数没有啥讲头,就是把映射的虚拟内存空间释放,并且销毁对应的文件描述符。
函数FbGetBuffer()
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这个函数实际上是把ioctl 函数返回的fb_var_screeninfo 结构体中存储的与LCD有关的信息和内存映射基地址存储到结构体ptDispBuff)中。
ptDispBuff->iXres 中存储的是LCD屏一行有多少个像素点。
ptDispBuff->iYres 中存储的是LCD屏一列有多少个像素点。
ptDispBuff->iBpp 中存储的是一个像素点用多个位表示。
ptDispBuff->buff的值fb_base是全局变量,是函数DeviceInit()运行后得到的虚拟内存基地址。
函数FbFlushRegion()
static int FbFlushRegion(PRegion ptRegion, PDispBuff ptDispBuff)
{
return 0;
}
- 1
- 2
- 3
- 4
这个函数没有具体的语句,主要是两个输入参数,一个是在头文件中定义的结构体struct Region结针
ptRegion,另一个是字符串指针buffer。
之所以没有具体的语句,是因为咱们开发板上的LCD在Framebuffer数据变化后,自动就显示最新的了,所以不需要再去作刷新操作。
当然,这其中也离不开语句:
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
- 1
中参数MAP_SHARED
的作用。
这里之所以写出这个函数,是为了程序能适应更多的显示设备。
初始化显示系统的顶层顶结体struct DispOpr
static DispOpr g_tFramebufferOpr = {
.name = "fb",
.DeviceInit = FbDeviceInit,
.DeviceExit = FbDeviceExit,
.GetBuffer = FbGetBuffer,
.FlushRegion = FbFlushRegion,
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
函数FramebufferInit()
void FramebufferInit(void)
{
RegisterDisplay(&g_tFramebufferOpr);
}
- 1
- 2
- 3
- 4
这个函数调用disp_manager.c
中的注册函数RegisterDisplay,并把初始化的结构体struct DispOpr
的实例&g_tFramebufferOpr
作为参数传入。
文件“disp_manager.c”:上层应用程序和下层设备管理程序的中间过渡层
需要的全局变量
static PDispOpr g_DispDevs = NULL;
static PDispOpr g_DispDefault = NULL;
static DispBuff g_tDispBuff;
static int line_width;
static int pixel_width;
- 1
- 2
- 3
- 4
- 5
要点分析:
PDispOpr是之前在头文件里定义的实现FrameBuffer的重要结构体struct DispOpr
的指针类型,相关代码如下:
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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这里定义了两个PDispOpr
类型的变量,分别为g_DispDevs
和 g_DispDefault
。根据下文的分析可知,g_DispDevs
用于存储最新注册的显示设备结构体, g_DispDefault
存储用户选择的设备的结构体,用户选择通过函数SelectDefaultDisplay()
实现。
DispBuff是之前在头文件里定义的结构体类型struct DispBuff
:
typedef struct DispBuff {
int iXres;
int iYres;
int iBpp;
char *buff;
}DispBuff, *PDispBuff;
- 1
- 2
- 3
- 4
- 5
- 6
line_width:是LCD一行所占的字节数。
pixel_width:是一个像素所用的字节数。
绘图函数PutPixel()
这个函数已在博文 IMX6ULL开发板基础实验:Framebuffer驱动程序的简单应用实例代码详细分析中进行了详细学习,这里不再赘述,代码如下:
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;
}
- 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
函数DisplayInit()
:这个函数实现设备的实始化
void DisplayInit(void)
{
extern void FramebufferInit(void);
FramebufferInit();
}
- 1
- 2
- 3
- 4
- 5
- 6
函数RegisterDisplay()
会调用framebuffer.c中的函数FramebufferInit()
,函数FramebufferInit()
的详细介绍见上文。
函数RegisterDisplay()
:实现注册显示设备结构体struct DispOpr
void RegisterDisplay(PDispOpr ptDispOpr)
{
ptDispOpr->ptNext = g_DispDevs;
g_DispDevs = ptDispOpr;
}
- 1
- 2
- 3
- 4
- 5
输入参数是下面这个结构体的指针类型:
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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这个函数RegisterDisplay()实现多个结构体struct DispOpr
的链表结构,从而实现设备的注册,具体原理如下:
假设现在我传入struct DispOpr 结构体指针
:g_DispDevs_2
,那么首先会让g_DispDevs_2
的成员ptNext指向g_DispDevs
,然后再让g_DispDevs 指向这个新的结构体指针:g_DispDevs_2
,也就是说g_DispDevs中永远是最新的结构体struct DispOpr
的指针,而之前的结构体通过链表进行了存储。
函数RegisterDisplay用于向结构体struct DispOpr
的链接中加入新的结构体,从而实现设备的注册,说白了,设备的注册就是有完整的结构体struct DispOpr
,并加入到链接中。
函数SelectDefaultDisplay()
:显示设备选择函数
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
根据对上一个函数RegisterDisplay()
的分析,我们发现指针变量g_DispDevs中永远存储的是最新的显示设备的结构体,但同时也形也了一个链表,所以函数SelectDefaultDisplay()
能通过对字符串char *name
的比较,然后遍历链表中的所有结构体,来通过设备名字找到你所选择的设备。并把设备结构体存储中在g_DispDefault
中。
函数InitDefaultDisplay()
:当前选择的显示设备进行初始化
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
值得注意的是,下面这段代码是有问题的。
ret = g_DispDefault->GetBuffer(&g_tDispBuff);
if (ret)
{
printf("GetBuffer err\n");
return -1;
}
- 1
- 2
- 3
- 4
- 5
- 6
问题在于GetBuffer()这个函数其实它的返回值应该始终为0才对,这里的GetBuffer()的代码如下:
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
从这段代码来看,只要这段代码编译通过并且执行了,那么返回值都为0,所以韦老师的这段代码显然不太严谨。
函数GetDisplayBuffer():获取结构体struct DispBuff
的指针
PDispBuff GetDisplayBuffer(void)
{
return &g_tDispBuff;
}
- 1
- 2
- 3
- 4
typedef struct DispBuff {
int iXres;
int iYres;
int iBpp;
char *buff;
}DispBuff, *PDispBuff;
- 1
- 2
- 3
- 4
- 5
- 6
获取结构体struct DispBuff
的指针后,方便函数static int FbFlushRegion(PRegion ptRegion, PDispBuff ptDispBuff)
的调用,因为这里面有个参数是PDispBuff ptDispBuf
这里要分析一个问题,g_tDispBuff的值是何时有的?g_tDispBuff是在文件disp_manager.c
中定义的,在函数InitDefaultDisplay()
中有代码:
ret = g_DispDefault->GetBuffer(&g_tDispBuff);
- 1
GetBuffer的代码如下:
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
可见通过这个调用,相关的分辨率,像素位深度、内存基址都通过指过形参的指针传递放到了结构变量g_tDispBuff
中。
函数FlushDisplayRegion():对指定的区域进行刷新操作
int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff)
{
return g_DispDefault->FlushRegion(ptRegion, ptDispBuff);
}
- 1
- 2
- 3
- 4
文件disp_test.c
:显示系统测试实例
功能描述
测试实例的功能是在屏幕上显示字符A,这个字符A是用点阵的数据格式来存储的。
一堆需要的头文件
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
这些需要的头文件已经在博文 http://iyenn.com/rec/1709395.html 中进行了介绍。
ASCII码点阵库
这个点阵库是在Linux内核中的文件Font_8×16.c中提取出来的。
#define FONTDATAMAX 4096
static const unsigned char fontdata_8x16[FONTDATAMAX] = {
/* 0 0x00 '^@' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
/* 1 0x01 '^A' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x7e, /* 01111110 */
0x81, /* 10000001 */
0xa5, /* 10100101 */
0x81, /* 10000001 */
0x81, /* 10000001 */
0xbd, /* 10111101 */
.......
- 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
这里边一共有256个字符,每个字符是16个字节,所以需要的数组大小为 16*256=4096个,也就是FONTDATAMAX定义的大小。
函数lcd_put_ascii()
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); /* 黑 */
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
这个函数就是调用之前写好的函数像素描点函数PutPixel(),把每个具体的字符的每一个点都画到LCD屏上去。
参数x和y分别代表每个字符的左上顶点的坐标。
主函数main()分析
int main(int argc, char **argv)
{
Region region;
PDispBuff ptBuffer;
DisplayInit();
SelectDefaultDisplay("fb");
InitDefaultDisplay();
lcd_put_ascii(100, 100, 'A');
region.iLeftUpX = 100;
region.iLeftUpY = 100;
region.iWidth = 8;
region.iHeigh = 16;
ptBuffer = GetDisplayBuffer();
FlushDisplayRegion(®ion, ptBuffer);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
首先调用DisplayInit();
注册好一个显示的结构体g_tFramebufferOpr
,这个结构体表明这个设备的名字是fb
static DispOpr g_tFramebufferOpr = {
.name = "fb",
.DeviceInit = FbDeviceInit,
.DeviceExit = FbDeviceExit,
.GetBuffer = FbGetBuffer,
.FlushRegion = FbFlushRegion,
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
然后用SelectDefaultDisplay("fb");
选择当前使用的设备。
选择好当前设备后再用InitDefaultDisplay();
初始化当前设备。
初始当前设备后就可以用lcd_put_ascii(100, 100, 'A');
绘图了。
后面的代码:
region.iLeftUpX = 100;
region.iLeftUpY = 100;
region.iWidth = 8;
region.iHeigh = 16;
ptBuffer = GetDisplayBuffer();
FlushDisplayRegion(®ion, ptBuffer);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在IMX6ULL开发析,用LCD显示字符的这个实验中并没有用,因为咱们的LCD配合下面这句代码的参数MAP_SHARED
,只需要内存中的值改变了,那么屏幕上的内容也变了,并不需要再手动去刷新区域。
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
- 1
编译并下载到板子上测试
首先按博文 http://iyenn.com/rec/1709618.html编译出目标文件
把test文件复制到NFS目录/home/book/nfs_rootfs
中
打开串口…
启动开发板…
测试能否Ping通…
挂载网络文件系统…
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
- 1
把test文件复制到开发板的用户home目录下的myprogram目录
cp /mnt/test ~/myprogram/test
- 1
给这个文件添加执行权限
chmod +x ~/myprogram/test
- 1
运行之前在博文:
IMX6ULL开发板把屏幕刷黑(黑屏)的程序
中编译好的把屏幕刷黑的程序把屏蔽刷黑,如果不刷黑是看不出效果的,因为我这个测试程序的效果就是屏幕上显示一个白色的字符A。
~/myprogram/draw_lcd_black
- 1
然后再执行咱们这里的测试程序:
~/myprogram/test
- 1
因为显示的字符A比较小,所以这里拍照看不出效果,就略去效果图了。
附完整源代码



评论记录:
回复评论: