目录
前言
一、项目介绍和应用
1.简单易用
2.软件可配置、易扩展
3.纯 C 语言编程
4.类似界面应用
二、项目总体框架
三、显示系统
1.显示系统数据结构抽象
(1)common.h
(2)disp_manager.h
2.Framebuffer编程
(1)framebuffer.c
3.显示管理
(1)disp_manager.c
4.测试单元
(1)测试代码
(2)通用Makefile
四、上机测试
前言
该项目是韦东山老师Linux入门基础课程的第一个项目,在这里我用的是IMX6ULL开发板。通过学习这个项目,可以学到良好的编程规范,面向对象的编程思想,对事物的抽象能力,对整个系统的把控能力。
一、项目介绍和应用
电子产品量产测试与烧写工具,这是一套软件,用在我们的实际生产中, 有如下特点:
1.简单易用
把这套软件烧写在 SD 卡上,插到 IMX6ULL 板子里并启动,它就会自动测试各个模块、烧写 EMMC 系统。
工人只要按照说明接入几个模块,就可以完成整个测试、烧写过程。
测试结果一目了然:等 LCD 上所有模块的图标都变绿时,就表示测试通过。
2.软件可配置、易扩展
通过配置文件添加测试项,可以添加不限个数的测试项。
每个测试项有自己的测试程序,测试通过后把结果发送给 GUI 即可。各个测试程序互不影响。
3.纯 C 语言编程
工具设计的界面,它可以一边测试一边烧写:

上图中的 led、speaker 按钮,可以点击:
1.当你看到 LED 闪烁时,就点击 led 按钮,它变成绿色表示测试通过;
2. 当你从耳机里听到声音时,就点击 speaker 按钮,它变成绿色表示测试通过。
- 其他按钮无法点击,接上对应模块后会自动测试,测试通过时图标就会变绿。
 - 上图中的蓝色按钮表示烧写 EMMC 的进度,烧写成功后它也会变绿。
 - LCD 上所有图标都变绿时,就表示测试、烧写全部完成;某项保持红色的话,就表示对应模块测试失败。
 
4.类似界面应用

二、项目总体框架
在软件编程当中,可以把一个项目拆分成各个子系统,并且这些子系统跟业务无关,以后还可以用在其他项目上。对于一个子系统,可以抽象出它的对外接口,减少与其他模块的耦合,方便扩展。

接下来先分析他的第一个框架,显示系统。
三、显示系统
1.显示系统数据结构抽象
我们添加的一个显示管理器中有Framebufler和web输出,对于两个不同的设备我们可以抽象出同一个结构体类型。

(1)common.h
该头文件用来包含通用区域结构体
#ifndef _COMMON_H
#define _COMMON_H
#ifndef NULL
#define NULL (void *)0
#endif
typedef struct Region {
	int iLeftUpX;
	int iLeftUpY;
	int iWidth;
	int iHeigh;
}Region, *PRegion;
#endif
 
(2)disp_manager.h
#ifndef _DISP_MANAGER_H
#define _DISP_MANAGER_H
 
#include <common.h>
typedef struct DispBuff {
	int iXres;
	int iYres;
	int iBpp;
	char *buff;
}DispBuff, *PDispBuff;
typedef struct DispOpr {
	char *name;
	int (*DeviceInit)(void);
	int (*DeviceExit)(void);
	int (*GetBuffer)(PDispBuff ptPDispBuff);
	int (*FlushRegion)(PRegion ptRegion, PDispBuff ptPDispBuff);
	struct DispOpr *ptNext;
}DispOpr, *PDispOpr;
int PutPixel(int x, int y, unsigned int dwColor);
void RegisterDisplay(PDispOpr ptPDispOpr);
int SelectDefaultDisplay(char *name);
int InitDefaultDisplay(void);
PDispBuff GetDisplayBuffer(void);
int FlushDisplayRegion(PRegion ptPRegion, PDispBuff ptPDispBuff);
void DisplaySystemRegister(void);
#endif
 
2.Framebuffer编程
Framebuffer编程原理和实操可以看:
(1)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)
{
	munmap(fb_base, 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  = (char *)fb_base;
	return 0;
}
static int FbFlushRegion(PRegion ptRegion, PDispBuff ptPDispBuff)
{
	return 0;
}
static DispOpr g_tFramebufferOpr = {
	.name        = "fb",
	.DeviceInit  = FbDeviceInit,
	.DeviceExit  = FbDeviceExit,
	.GetBuffer   = FbGetBuffer,
	.FlushRegion = FbFlushRegion,
};
void FramebufferRegister(void)
{
	RegisterDisplay(&g_tFramebufferOpr);
}
 
3.显示管理
上层函数想要选择哪个设备进行显示,需要中间加一个函数进行选择,起到承上启下的作用,用来实现显示管理,是操作Framebuffer还是WEB设备,需要我们选择某个模块,以便于可以提供一些函数,描点等。
(1)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 ptPDispOpr)
{
	ptPDispOpr->ptNext = g_DispDevs;
	g_DispDevs = ptPDispOpr;
}
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 0;
}
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;
}
PDispBuff GetDisplayBuffer(void)
{
	return &g_tDispBuff;
}
int FlushDisplayRegion(PRegion ptPRegion, PDispBuff ptPDispBuff)
{
	return g_DispDefault->FlushRegion(ptPRegion, ptPDispBuff);
}
void DisplaySystemRegister(void)
{
	extern void FramebufferRegister(void);
	FramebufferRegister();
}
 
4.测试单元

display目录存放 framebuffer.c,disp_manager.c 文件
include目录存放头文件
unittest目录存放单元测试.c文件
(1)测试代码
disp_test.c
(2)通用Makefile
通用Makefile的原理和使用可以看:
顶层目录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)
	 
顶层目录Makefile.build
PHONY := __build
__build:
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=
include Makefile
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y))   : c/ d/
# __subdir-y  : c d
# subdir-y    : c d
__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)
# c/built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
ifneq ($(dep_files),)
  include $(dep_files)
endif
PHONY += $(subdir-y)
__build : $(subdir-y) built-in.o
$(subdir-y):
	make -C $@ -f $(TOPDIR)/Makefile.build
built-in.o : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^
dep_file = .$@.d
%.o : %.c
	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
	
.PHONY : $(PHONY) 
底层目录display中的Makefile
EXTRA_CFLAGS  := 
CFLAGS_file.o := 
obj-y += disp_manager.o
obj-y += framebuffer.o 
底层目录unittest中的Makefile
EXTRA_CFLAGS  := 
CFLAGS_file.o := 
obj-y += disp_test.o 
四、上机测试
打开ubuntu,将交叉编译编译成功的文件复制到nfs目录。

上电开发板,挂载 Ubuntu 的 NFS 目录,详细可看:
开发板挂载 Ubuntu 的 NFS 目录_开发板nfs挂载ubuntu-CSDN博客
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt 
进入NFS目录,编译测试:





















