目录
前言
一、项目介绍和应用
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目录,编译测试: