嵌入式应用实例→电子产品量产工具→显示系统的代码阅读和上机测试记录

系统框图

见链接 https://kdocs.cn/l/ckwixdBIP6lf

头文件disp_manager.h:定义与显示系统有关的结构体和函数

路径:“\source\01_display_struct\disp_manager.h”

宏定义空指针

#define NULL (void *)0

详细介绍见 https://blog.youkuaiyun.com/wenhao_ir/article/details/144516141
这个NULL的宏定义后面在disp_manager.c中会用到,就是下面的两句代码会运到:

static PDispOpr g_DispDevs = NULL;
static PDispOpr g_DispDefault = NULL;

结构体DispBuff

typedef struct DispBuff {
	int iXres;
	int iYres;
	int iBpp;
	char *buff;
}DispBuff, *PDispBuff;

DispBuff结构体用于存储设备返回的x方向的分辨率(iXres)、y方向的分辨率(iYres)、每个像素点用多少位表示(iBpp)、虚拟内存映射的基指针buff。

结构体Region

typedef struct Region {
	int iLeftUpX;
	int iLeftUpY;
	int iWidth;
	int iHeigh;
}Region, *PRegion;

这个结构体用于存储一个具体的区域,这个在区域刷新显示时会用到。

结构体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;

这个结构体就是一个设备的底层信息的汇总结构体,是该显示系统中非常重要的结构体。
注意:成员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);

具体的这些函数的作用请见下面对各源文件中各函数的分析。

文件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;

前面的static表示这些变量仅限在本文件内使用,如果想深入了解这些变量,把博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/144487608 看一遍就行了。
下面是详细解释:
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;
}

这个函数实际上就是博文 IMX6ULL开发板基础实验:Framebuffer驱动程序的简单应用实例代码详细分析里的代码,主要作用就是打开Framebuffer设备文件,获得文件描述符,并映射到程序的虚拟内存。

函数FbDeviceExit()

static int FbDeviceExit(void)
{
	munmap(fb_base, screen_size);
	close(fd_fb);
	return 0;
}

这个函数没有啥讲头,就是把映射的虚拟内存空间释放,并且销毁对应的文件描述符。

函数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;
}

这个函数实际上是把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;
}

这个函数没有具体的语句,主要是两个输入参数,一个是在头文件中定义的结构体struct Region结针
ptRegion,另一个是字符串指针buffer。
之所以没有具体的语句,是因为咱们开发板上的LCD在Framebuffer数据变化后,自动就显示最新的了,所以不需要再去作刷新操作。
当然,这其中也离不开语句:

fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);

中参数MAP_SHARED的作用。
这里之所以写出这个函数,是为了程序能适应更多的显示设备。

初始化显示系统的顶层顶结体struct DispOpr

static DispOpr g_tFramebufferOpr = {
	.name        = "fb",
	.DeviceInit  = FbDeviceInit,
	.DeviceExit  = FbDeviceExit,
	.GetBuffer   = FbGetBuffer,
	.FlushRegion = FbFlushRegion,
};

函数FramebufferInit()

void FramebufferInit(void)
{
	RegisterDisplay(&g_tFramebufferOpr);
}

这个函数调用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;

要点分析:
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;

这里定义了两个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;

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;
}

函数DisplayInit():这个函数实现设备的实始化

void DisplayInit(void)
{
	
	extern void FramebufferInit(void);
	FramebufferInit();
}

函数RegisterDisplay()会调用framebuffer.c中的函数FramebufferInit(),函数FramebufferInit()的详细介绍见上文。

函数RegisterDisplay():实现注册显示设备结构体struct DispOpr

void RegisterDisplay(PDispOpr ptDispOpr)
{
	ptDispOpr->ptNext = g_DispDevs;
	g_DispDevs = ptDispOpr;
}

输入参数是下面这个结构体的指针类型:

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;

这个函数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;
}

根据对上一个函数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;
}

值得注意的是,下面这段代码是有问题的。

	ret = g_DispDefault->GetBuffer(&g_tDispBuff);
	if (ret)
	{
		printf("GetBuffer err\n");
		return -1;
	}

问题在于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;
}

从这段代码来看,只要这段代码编译通过并且执行了,那么返回值都为0,所以韦老师的这段代码显然不太严谨。

函数GetDisplayBuffer():获取结构体struct DispBuff的指针

PDispBuff GetDisplayBuffer(void)
{
	return &g_tDispBuff;
}
typedef struct DispBuff {
	int iXres;
	int iYres;
	int iBpp;
	char *buff;
}DispBuff, *PDispBuff;

获取结构体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);

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;
}

可见通过这个调用,相关的分辨率,像素位深度、内存基址都通过指过形参的指针传递放到了结构变量g_tDispBuff中。

函数FlushDisplayRegion():对指定的区域进行刷新操作

int FlushDisplayRegion(PRegion ptRegion, PDispBuff ptDispBuff)
{
	return g_DispDefault->FlushRegion(ptRegion, ptDispBuff);
}

文件disp_test.c:显示系统测试实例

功能描述

测试实例的功能是在屏幕上显示字符A,这个字符A是用点阵的数据格式来存储的。

一堆需要的头文件

#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>

这些需要的头文件已经在博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/144487608 中进行了介绍。

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 */
.......

这里边一共有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); /* 黑 */
			}
		}
	}
}

这个函数就是调用之前写好的函数像素描点函数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(&region, ptBuffer);
	
	return 0;	
}

首先调用DisplayInit();注册好一个显示的结构体g_tFramebufferOpr,这个结构体表明这个设备的名字是fb

static DispOpr g_tFramebufferOpr = {
	.name        = "fb",
	.DeviceInit  = FbDeviceInit,
	.DeviceExit  = FbDeviceExit,
	.GetBuffer   = FbGetBuffer,
	.FlushRegion = FbFlushRegion,
};

然后用SelectDefaultDisplay("fb");选择当前使用的设备。

选择好当前设备后再用InitDefaultDisplay();初始化当前设备。

初始当前设备后就可以用lcd_put_ascii(100, 100, 'A');绘图了。

后面的代码:

	region.iLeftUpX = 100;
	region.iLeftUpY = 100;
	region.iWidth   = 8;
	region.iHeigh   = 16;

	ptBuffer = GetDisplayBuffer();
	FlushDisplayRegion(&region, ptBuffer);

在IMX6ULL开发析,用LCD显示字符的这个实验中并没有用,因为咱们的LCD配合下面这句代码的参数MAP_SHARED,只需要内存中的值改变了,那么屏幕上的内容也变了,并不需要再手动去刷新区域。

fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);

编译并下载到板子上测试

首先按博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/144532544编译出目标文件
在这里插入图片描述
把test文件复制到NFS目录/home/book/nfs_rootfs
在这里插入图片描述
打开串口…

启动开发板…

测试能否Ping通…

挂载网络文件系统…

mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt

把test文件复制到开发板的用户home目录下的myprogram目录

cp /mnt/test ~/myprogram/test

给这个文件添加执行权限

chmod +x ~/myprogram/test

运行之前在博文:
IMX6ULL开发板把屏幕刷黑(黑屏)的程序
中编译好的把屏幕刷黑的程序把屏蔽刷黑,如果不刷黑是看不出效果的,因为我这个测试程序的效果就是屏幕上显示一个白色的字符A。

~/myprogram/draw_lcd_black

然后再执行咱们这里的测试程序:

~/myprogram/test

因为显示的字符A比较小,所以这里拍照看不出效果,就略去效果图了。

附完整源代码

https://pan.baidu.com/s/1alxPD7geby7XCUU4l2W2Qg?pwd=6xjw

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值